Compare commits
15 Commits
c7df3db502
...
kub-stage
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc721da65f | ||
|
|
2fc7733c84 | ||
|
|
82068bf8f8 | ||
|
|
f00ade566c | ||
|
|
5a4774b1f3 | ||
|
|
2bfac7afaa | ||
|
|
00290e91fd | ||
|
|
5c8c0418ae | ||
|
|
6c2d011361 | ||
| 200825064a | |||
|
|
59d9a0644a | ||
|
|
dea8de348e | ||
|
|
2c899f6029 | ||
|
|
0262ae68c4 | ||
|
|
a5904cd077 |
@@ -34,7 +34,7 @@ jobs:
|
||||
mkdir -p /etc/docker
|
||||
cat > /etc/docker/daemon.json << 'DAEMON'
|
||||
{
|
||||
"insecure-registries": ["194.5.195.53:30080", "gitea-svc:3000"]
|
||||
"insecure-registries": ["git.foursat.afrino.co", "gitea-svc:3000"]
|
||||
}
|
||||
DAEMON
|
||||
mkdir -p ~/.docker
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -1,15 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
WORKDIR /src
|
||||
COPY src/*.sln ./ 2>/dev/null || true
|
||||
COPY src/*/*.csproj ./
|
||||
RUN for file in *.csproj; do mkdir -p "${file%.*}" && mv "$file" "${file%.*}/"; done 2>/dev/null || true
|
||||
RUN dotnet restore "BackOffice/BackOffice.csproj" || dotnet restore
|
||||
COPY src/ ./
|
||||
RUN dotnet publish "BackOffice/BackOffice.csproj" -c Release -o /app/publish
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["dotnet", "BackOffice.dll"]
|
||||
131
src/BackOffice.Main/Pages/Payment/ManualMembershipPayment.razor
Normal file
131
src/BackOffice.Main/Pages/Payment/ManualMembershipPayment.razor
Normal file
@@ -0,0 +1,131 @@
|
||||
@page "/payment/membership"
|
||||
@using BackOffice.BFF.ManualPayment.Protobuf
|
||||
@using BackOffice.Main.Pages.AutoComplete
|
||||
@attribute [Authorize(Roles = "Admin,SuperAdmin")]
|
||||
|
||||
<PageTitle>پرداخت دستی عضویت</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h5">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Payment" Class="ml-2" />
|
||||
پرداخت دستی عضویت
|
||||
</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<!-- انتخاب کاربر -->
|
||||
<MudItem xs="12" md="6">
|
||||
<UserAutoComplete
|
||||
Label="انتخاب کاربر *"
|
||||
@bind-SelectedUserId="_userId"
|
||||
HelperText="جستجو با نام، موبایل یا شناسه" />
|
||||
</MudItem>
|
||||
|
||||
<!-- مبلغ -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField
|
||||
@bind-Value="_amount"
|
||||
Label="مبلغ (ریال) *"
|
||||
Min="0"
|
||||
Step="10000"
|
||||
Format="N0"
|
||||
Variant="Variant.Outlined"
|
||||
HelperText="مبلغ پرداختی به ریال" />
|
||||
</MudItem>
|
||||
|
||||
<!-- شماره مرجع -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField
|
||||
@bind-Value="_referenceNumber"
|
||||
Label="شماره مرجع *"
|
||||
Variant="Variant.Outlined"
|
||||
MaxLength="50"
|
||||
HelperText="شماره مرجع تراکنش یا فیش" />
|
||||
</MudItem>
|
||||
|
||||
<!-- توضیحات -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField
|
||||
@bind-Value="_description"
|
||||
Label="توضیحات"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
MaxLength="500"
|
||||
HelperText="توضیحات اختیاری" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@if (_showResult)
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Class="mt-4" ShowCloseIcon CloseIconClicked="@(() => _showResult = false)">
|
||||
<MudText>@_resultMessage</MudText>
|
||||
<MudText Typo="Typo.caption">شماره تراکنش: @_transactionId</MudText>
|
||||
<MudText Typo="Typo.caption">شماره سفارش: @_orderId</MudText>
|
||||
<MudText Typo="Typo.caption">موجودی جدید کیف پول: @_newWalletBalance.ToString("N0") ریال</MudText>
|
||||
</MudAlert>
|
||||
}
|
||||
</MudCardContent>
|
||||
|
||||
<MudCardActions>
|
||||
<MudButton
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Check"
|
||||
OnClick="ProcessPayment"
|
||||
Disabled="@_isProcessing">
|
||||
@if (_isProcessing)
|
||||
{
|
||||
<MudProgressCircular Class="ml-2" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>در حال ثبت...</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText>ثبت پرداخت</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
|
||||
<MudButton
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Default"
|
||||
StartIcon="@Icons.Material.Filled.Clear"
|
||||
OnClick="ResetForm"
|
||||
Disabled="@_isProcessing">
|
||||
پاک کردن فرم
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
|
||||
<!-- راهنما -->
|
||||
<MudCard Class="mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Info" Class="ml-2" />
|
||||
راهنما
|
||||
</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.body2" Class="mb-2">این صفحه برای ثبت پرداختهای دستی عضویت استفاده میشود. پس از ثبت:</MudText>
|
||||
<MudList Dense="true">
|
||||
<MudListItem Icon="@Icons.Material.Filled.AccountBalance">
|
||||
<MudText Typo="Typo.body2">مبلغ به کیف پول کاربر (Balance و DiscountBalance) اضافه میشود</MudText>
|
||||
</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.History">
|
||||
<MudText Typo="Typo.body2">لاگ تغییرات کیف پول ثبت میشود</MudText>
|
||||
</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Receipt">
|
||||
<MudText Typo="Typo.body2">تراکنش با وضعیت موفق ثبت میشود</MudText>
|
||||
</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.ShoppingCart">
|
||||
<MudText Typo="Typo.body2">سفارش خرید پکیج عضویت ثبت میشود</MudText>
|
||||
</MudListItem>
|
||||
</MudList>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,103 @@
|
||||
using BackOffice.BFF.ManualPayment.Protobuf;
|
||||
using BackOffice.Main.Components;
|
||||
using Grpc.Core;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BackOffice.Main.Pages.Payment;
|
||||
|
||||
public partial class ManualMembershipPayment
|
||||
{
|
||||
[Inject] private ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
|
||||
[Inject] private ISnackbar Snackbar { get; set; } = default!;
|
||||
|
||||
private long? _userId;
|
||||
private long _amount = 0;
|
||||
private string _referenceNumber = string.Empty;
|
||||
private string? _description;
|
||||
|
||||
private bool _isProcessing = false;
|
||||
private bool _showResult = false;
|
||||
private string _resultMessage = string.Empty;
|
||||
private long _transactionId = 0;
|
||||
private long _orderId = 0;
|
||||
private long _newWalletBalance = 0;
|
||||
|
||||
private async Task ProcessPayment()
|
||||
{
|
||||
// Validation
|
||||
if (!_userId.HasValue || _userId.Value <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفا کاربر را انتخاب کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_amount <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفا مبلغ معتبری وارد کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_referenceNumber))
|
||||
{
|
||||
Snackbar.Add("لطفا شماره مرجع را وارد کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isProcessing = true;
|
||||
_showResult = false;
|
||||
|
||||
var request = new ProcessManualMembershipPaymentRequest
|
||||
{
|
||||
UserId = _userId.Value,
|
||||
Amount = _amount,
|
||||
ReferenceNumber = _referenceNumber
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_description))
|
||||
{
|
||||
request.Description = _description;
|
||||
}
|
||||
|
||||
var response = await ManualPaymentClient.ProcessManualMembershipPaymentAsync(request);
|
||||
|
||||
_resultMessage = response.Message;
|
||||
_transactionId = response.TransactionId;
|
||||
_orderId = response.OrderId;
|
||||
_newWalletBalance = response.NewWalletBalance;
|
||||
_showResult = true;
|
||||
|
||||
Snackbar.Add("پرداخت دستی با موفقیت ثبت شد", Severity.Success);
|
||||
|
||||
// Reset form
|
||||
await Task.Delay(2000);
|
||||
ResetForm();
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در ثبت پرداخت: {ex.Status.Detail}", Severity.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطای غیرمنتظره: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetForm()
|
||||
{
|
||||
_userId = null;
|
||||
_amount = 0;
|
||||
_referenceNumber = string.Empty;
|
||||
_description = null;
|
||||
_showResult = false;
|
||||
_resultMessage = string.Empty;
|
||||
_transactionId = 0;
|
||||
_orderId = 0;
|
||||
_newWalletBalance = 0;
|
||||
}
|
||||
}
|
||||
@@ -116,13 +116,13 @@
|
||||
|
||||
<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.12" />
|
||||
|
||||
<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" />
|
||||
|
||||
@@ -134,9 +134,9 @@
|
||||
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Health.Protobuf" Version="1.0.4" />
|
||||
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.ManualPayment.Protobuf" Version="0.0.2" />
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.ManualPayment.Protobuf" Version="0.0.3" />
|
||||
|
||||
<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" />
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
<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.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.UserRole.Protobuf.Protos.UserRole;
|
||||
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
||||
using BackOffice.BFF.Commission.Protobuf;
|
||||
using BackOffice.BFF.NetworkMembership.Protobuf;
|
||||
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||
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
|
||||
// using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
|
||||
@@ -32,9 +35,9 @@ using Microsoft.AspNetCore.Components.Authorization;
|
||||
using MudBlazor.Services;
|
||||
using System.Text.Json;
|
||||
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.NetworkMembership.Protos;
|
||||
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
@@ -60,6 +63,9 @@ public static class ConfigureServices
|
||||
services.AddMudServices();
|
||||
services.AddGrpcServices(configuration);
|
||||
|
||||
// Persian DateTime Service
|
||||
services.AddSingleton<BackOffice.Services.IPersianDateTimeService, BackOffice.Services.PersianDateTimeService>();
|
||||
|
||||
// Application Services
|
||||
services.AddScoped<BackOffice.Services.Authorization.IAuthorizationService, BackOffice.Services.Authorization.AuthorizationService>();
|
||||
// TODO: Re-enable when proto projects are created
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Blazored.LocalStorage;
|
||||
@@ -27,12 +27,53 @@ public class ApiAuthenticationStateProvider : AuthenticationStateProvider
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var token = handler.ReadJwtToken(savedToken);
|
||||
|
||||
// Parse the token payload to handle array-based roles
|
||||
var claims = new List<Claim>();
|
||||
|
||||
foreach (var claim in token.Claims)
|
||||
{
|
||||
// Handle role claims specially to support multiple roles
|
||||
if (claim.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" ||
|
||||
claim.Type == ClaimTypes.Role ||
|
||||
claim.Type == "role")
|
||||
{
|
||||
// Check if the value is a JSON array
|
||||
if (claim.Value.TrimStart().StartsWith("["))
|
||||
{
|
||||
try
|
||||
{
|
||||
var roles = JsonSerializer.Deserialize<string[]>(claim.Value);
|
||||
if (roles != null)
|
||||
{
|
||||
foreach (var role in roles)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, role));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If parsing fails, add as single claim
|
||||
claims.Add(new Claim(ClaimTypes.Role, claim.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single role value
|
||||
claims.Add(new Claim(ClaimTypes.Role, claim.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
claims.Add(claim);
|
||||
}
|
||||
}
|
||||
|
||||
var AuthenticationState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(token.Claims, "jwt")));
|
||||
var authenticationState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")));
|
||||
|
||||
return AuthenticationState;
|
||||
return authenticationState;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
|
||||
}
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Copy NuGet config and project file
|
||||
COPY ["BackOffice/NuGet.config", "NuGet.config"]
|
||||
COPY ["BackOffice/BackOffice.csproj", "BackOffice/"]
|
||||
RUN dotnet restore "BackOffice/BackOffice.csproj" --configfile NuGet.config
|
||||
COPY . .
|
||||
WORKDIR "/src/BackOffice"
|
||||
RUN dotnet publish "./BackOffice.csproj" -c Release -o /app/publish
|
||||
|
||||
# Restore dependencies
|
||||
RUN dotnet restore "BackOffice/BackOffice.csproj" --configfile NuGet.config
|
||||
|
||||
# Copy all source code
|
||||
COPY . .
|
||||
|
||||
# Build and publish
|
||||
WORKDIR "/src/BackOffice"
|
||||
RUN dotnet publish "BackOffice.csproj" -c Release -o /app/publish
|
||||
|
||||
# Runtime stage - nginx for Blazor WebAssembly
|
||||
FROM nginx:alpine AS final
|
||||
WORKDIR /usr/share/nginx/html
|
||||
|
||||
# Remove default nginx files
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
|
||||
# Copy published wwwroot (Blazor WASM output)
|
||||
COPY --from=build /app/publish/wwwroot .
|
||||
COPY <<'NGINX_CONF' /etc/nginx/conf.d/default.conf
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
NGINX_CONF
|
||||
|
||||
# Configure nginx for SPA routing
|
||||
RUN echo 'server { \
|
||||
listen 80; \
|
||||
server_name _; \
|
||||
location / { \
|
||||
root /usr/share/nginx/html; \
|
||||
try_files $uri $uri/ /index.html; \
|
||||
} \
|
||||
}' > /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/BackOffice/Pages/AutoComplete/WeekNumberPicker.razor
Normal file
21
src/BackOffice/Pages/AutoComplete/WeekNumberPicker.razor
Normal file
@@ -0,0 +1,21 @@
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
|
||||
<MudAutocomplete T="WeekInfo"
|
||||
Label="@Label"
|
||||
Value="@_selectedWeek"
|
||||
DebounceInterval="300"
|
||||
ValueChanged="@OnSelected"
|
||||
ToStringFunc="@(e => e?.WeekNumber ?? string.Empty)"
|
||||
SearchFunc="@Search"
|
||||
Variant="Variant.Outlined"
|
||||
Clearable="true"
|
||||
ShowProgressIndicator="true"
|
||||
CoerceText="@CoerceText"
|
||||
ResetValueOnEmptyText="true"
|
||||
OnClearButtonClick="@OnClear">
|
||||
<ItemTemplate Context="week">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText>@week.DisplayText</MudText>
|
||||
</MudStack>
|
||||
</ItemTemplate>
|
||||
</MudAutocomplete>
|
||||
107
src/BackOffice/Pages/AutoComplete/WeekNumberPicker.razor.cs
Normal file
107
src/BackOffice/Pages/AutoComplete/WeekNumberPicker.razor.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using BackOffice.Services;
|
||||
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||
using Grpc.Core;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BackOffice.Pages.AutoComplete;
|
||||
|
||||
public partial class WeekNumberPicker
|
||||
{
|
||||
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; } = default!;
|
||||
[Inject] public IPersianDateTimeService PersianDateTime { get; set; } = default!;
|
||||
|
||||
[Parameter] public string Label { get; set; } = "انتخاب هفته";
|
||||
[Parameter] public string? SelectedWeekNumber { get; set; }
|
||||
[Parameter] public EventCallback<string?> SelectedWeekNumberChanged { get; set; }
|
||||
[Parameter] public bool CoerceText { get; set; } = true; // اجازه ورود دستی
|
||||
[Parameter] public int FutureWeeksCount { get; set; } = 4;
|
||||
[Parameter] public int PastWeeksCount { get; set; } = 12;
|
||||
|
||||
private WeekInfo? _selectedWeek;
|
||||
private List<WeekInfo> _allWeeks = new();
|
||||
private bool _isLoaded = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadWeeks();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(SelectedWeekNumber) && _allWeeks.Any())
|
||||
{
|
||||
_selectedWeek = _allWeeks.FirstOrDefault(w => w.WeekNumber == SelectedWeekNumber);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SelectedWeekNumber) &&
|
||||
_selectedWeek?.WeekNumber != SelectedWeekNumber &&
|
||||
_isLoaded)
|
||||
{
|
||||
_selectedWeek = _allWeeks.FirstOrDefault(w => w.WeekNumber == SelectedWeekNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadWeeks()
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetAvailableWeeksRequest
|
||||
{
|
||||
FutureWeeksCount = FutureWeeksCount,
|
||||
PastWeeksCount = PastWeeksCount
|
||||
};
|
||||
|
||||
var response = await CommissionContract.GetAvailableWeeksAsync(request);
|
||||
|
||||
_allWeeks = new List<WeekInfo>();
|
||||
|
||||
// ترتیب: هفته جاری، محاسبه شده، در انتظار، آینده
|
||||
if (response.CurrentWeek != null)
|
||||
_allWeeks.Add(response.CurrentWeek);
|
||||
|
||||
if (response.CalculatedWeeks != null)
|
||||
_allWeeks.AddRange(response.CalculatedWeeks);
|
||||
|
||||
if (response.PendingWeeks != null)
|
||||
_allWeeks.AddRange(response.PendingWeeks);
|
||||
|
||||
if (response.FutureWeeks != null)
|
||||
_allWeeks.AddRange(response.FutureWeeks);
|
||||
|
||||
_isLoaded = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_allWeeks = new List<WeekInfo>();
|
||||
_isLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<WeekInfo>> Search(string value, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_isLoaded)
|
||||
await LoadWeeks();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return _allWeeks;
|
||||
|
||||
// جستجو در شماره هفته
|
||||
return _allWeeks
|
||||
.Where(w => w.WeekNumber.Contains(value, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task OnSelected(WeekInfo? selected)
|
||||
{
|
||||
_selectedWeek = selected;
|
||||
SelectedWeekNumber = selected?.WeekNumber;
|
||||
await SelectedWeekNumberChanged.InvokeAsync(SelectedWeekNumber);
|
||||
}
|
||||
|
||||
private async Task OnClear()
|
||||
{
|
||||
_selectedWeek = null;
|
||||
SelectedWeekNumber = null;
|
||||
await SelectedWeekNumberChanged.InvokeAsync(null);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using BackOffice.Pages.Club.Components
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">مدیریت اعضای باشگاه</MudText>
|
||||
|
||||
@@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using BackOffice.Pages.Club.Components;
|
||||
using Foursat.BackOffice.BFF.ClubMembership.Protobuf;
|
||||
using Foursat.BackOffice.BFF.ClubMembership.Protos;
|
||||
|
||||
namespace BackOffice.Pages.Club;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/club/statistics"
|
||||
|
||||
@using MudBlazor
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">آمار باشگاه</MudText>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@page "/commission/dashboard"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
@using MudBlazor
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">داشبورد کمیسیون</MudText>
|
||||
@@ -18,9 +19,9 @@
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<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.body2" Color="Color.Secondary">هفته @(_currentWeekNumber)</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">هفته @(_currentWeekNumberPersian)</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
@@ -28,9 +29,9 @@
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<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.body2" Color="Color.Secondary">مجموع بالانسهای فعال</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">مجموع تعادل های فعال</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
@@ -38,9 +39,9 @@
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<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.body2" Color="Color.Secondary">قیمت واحد بالانس</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">قیمت واحد تعادل</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
@@ -57,7 +58,9 @@
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||
@if (_poolData?.CalculatedAt != null)
|
||||
{
|
||||
@($"در تاریخ {_poolData.CalculatedAt.ToDateTime().ToLocalTime():yyyy/MM/dd}")
|
||||
var calculatedDate = _poolData.CalculatedAt.ToDateTime().ToLocalTime();
|
||||
var persianDate = PersianDateTime.ConvertToPersianDateTime(calculatedDate);
|
||||
@($"در تاریخ {persianDate}")
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -73,12 +76,88 @@
|
||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudText Typo="Typo.h6">انتخاب هفته:</MudText>
|
||||
<MudTextField @bind-Value="_currentWeekNumber"
|
||||
Label="شماره هفته"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
HelperText="فرمت: YYYY-Www (مثلاً 2025-W48)"
|
||||
Style="max-width: 200px;" />
|
||||
|
||||
@if (_allWeeks.Any())
|
||||
{
|
||||
<MudSelect T="string"
|
||||
Value="_currentWeekNumber"
|
||||
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
|
||||
{
|
||||
<WeekNumberPicker Label="شماره هفته"
|
||||
@bind-SelectedWeekNumber="_currentWeekNumber"
|
||||
CoerceText="true"
|
||||
Style="max-width: 250px;" />
|
||||
}
|
||||
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="LoadPoolData"
|
||||
@@ -92,11 +171,11 @@
|
||||
@if (_poolData != null)
|
||||
{
|
||||
<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">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه Pool:</strong></td>
|
||||
<td><strong>شناسه استخر:</strong></td>
|
||||
<td>@_poolData.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -104,15 +183,15 @@
|
||||
<td>@_poolData.WeekNumber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>مجموع Pool:</strong></td>
|
||||
<td><strong>مجموع استخر:</strong></td>
|
||||
<td>@_poolData.TotalPoolAmount.ToString("N0") ریال</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تعداد بالانسها:</strong></td>
|
||||
<td><strong>تعداد تعادل:</strong></td>
|
||||
<td>@_poolData.TotalBalances</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>ارزش هر بالانس:</strong></td>
|
||||
<td><strong>ارزش هر تعادل:</strong></td>
|
||||
<td>@_poolData.ValuePerBalance.ToString("N0") ریال</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -1,22 +1,90 @@
|
||||
using BackOffice.BFF.Commission.Protobuf;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||
using BackOffice.Services;
|
||||
|
||||
namespace BackOffice.Pages.Commission;
|
||||
|
||||
public partial class Dashboard
|
||||
{
|
||||
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
||||
[Inject] public IPersianDateTimeService PersianDateTime { get; set; }
|
||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||
|
||||
|
||||
[SupplyParameterFromQuery(Name = "week")]
|
||||
public string? WeekQueryParam { get; set; }
|
||||
|
||||
private bool _isLoading = true;
|
||||
private string _currentWeekNumber = string.Empty;
|
||||
private string _currentWeekNumberPersian = string.Empty; // هفته به شمسی
|
||||
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()
|
||||
{
|
||||
// محاسبه شماره هفته جاری
|
||||
_currentWeekNumber = GetCurrentWeekNumber();
|
||||
// بارگذاری لیست هفتههای قابل انتخاب
|
||||
await LoadAvailableWeeks();
|
||||
|
||||
// اگر query string آمده، از آن استفاده کن
|
||||
if (!string.IsNullOrWhiteSpace(WeekQueryParam))
|
||||
{
|
||||
_currentWeekNumber = WeekQueryParam;
|
||||
}
|
||||
else
|
||||
{
|
||||
// محاسبه شماره هفته جاری (میلادی برای API)
|
||||
_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);
|
||||
|
||||
// بهروزرسانی URL با query string
|
||||
var uri = NavigationManager.GetUriWithQueryParameter("week", weekNumber);
|
||||
NavigationManager.NavigateTo(uri, false);
|
||||
|
||||
await LoadPoolData();
|
||||
}
|
||||
|
||||
@@ -33,7 +101,7 @@ public partial class Dashboard
|
||||
};
|
||||
|
||||
_poolData = await CommissionContract.GetWeeklyCommissionPoolAsync(request);
|
||||
|
||||
Console.WriteLine(JsonSerializer.Serialize(_poolData));
|
||||
Snackbar.Add($"اطلاعات Pool هفته {_currentWeekNumber} بارگذاری شد", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -59,9 +127,16 @@ public partial class Dashboard
|
||||
{
|
||||
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);
|
||||
|
||||
|
||||
// Reload data after a delay
|
||||
await Task.Delay(2000);
|
||||
await LoadPoolData();
|
||||
@@ -77,7 +152,7 @@ public partial class Dashboard
|
||||
{
|
||||
var today = DateTime.Now;
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
@page "/commission/payouts"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using MudBlazor
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Payout های کاربران</MudText>
|
||||
<MudText Typo="Typo.h4" Class="mb-4"> پرداخت های کاربران</MudText>
|
||||
|
||||
<MudDataGrid T="UserCommissionPayoutModel"
|
||||
ServerData="@(new Func<GridState<UserCommissionPayoutModel>, Task<GridData<UserCommissionPayoutModel>>>(ServerReload))"
|
||||
@@ -14,22 +15,15 @@
|
||||
@ref="_gridData"
|
||||
Height="75vh">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">لیست Payout ها</MudText>
|
||||
<MudText Typo="Typo.h6">لیست پرداخت ها</MudText>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudTextField @bind-Value="_filterUserId"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true"
|
||||
Style="max-width: 150px;" />
|
||||
<MudTextField @bind-Value="_filterWeekNumber"
|
||||
Label="شماره هفته"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true"
|
||||
Placeholder="2025-W48"
|
||||
Style="max-width: 150px;" />
|
||||
<UserAutoComplete Label="جستجوی کاربر"
|
||||
@bind-SelectedUserId="_filterUserId" />
|
||||
<WeekNumberPicker Label="شماره هفته"
|
||||
@bind-SelectedWeekNumber="_filterWeekNumber"
|
||||
CoerceText="true"
|
||||
/>
|
||||
<MudSelect @bind-Value="_filterStatus"
|
||||
Label="وضعیت"
|
||||
Variant="Variant.Outlined"
|
||||
@@ -61,7 +55,14 @@
|
||||
</CellTemplate>
|
||||
</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="بالانسها">
|
||||
<CellTemplate>
|
||||
@@ -97,7 +98,10 @@
|
||||
|
||||
<PropertyColumn Property="x => x.Created" Title="تاریخ ایجاد">
|
||||
<CellTemplate>
|
||||
@context.Item.Created.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")
|
||||
@{
|
||||
var persianDate = PersianDateTime.ConvertToPersianDateTime(context.Item.Created.ToDateTime().ToLocalTime());
|
||||
}
|
||||
@persianDate
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using BackOffice.BFF.Commission.Protobuf;
|
||||
using BackOffice.BFF.Protobuf.Common;
|
||||
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using BackOffice.Pages.Commission.Components;
|
||||
using BackOffice.Services;
|
||||
|
||||
namespace BackOffice.Pages.Commission;
|
||||
|
||||
public partial class UserPayouts
|
||||
{
|
||||
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
||||
[Inject] public IPersianDateTimeService PersianDateTime { get; set; }
|
||||
|
||||
private MudDataGrid<UserCommissionPayoutModel> _gridData;
|
||||
private long? _filterUserId;
|
||||
@@ -21,23 +24,27 @@ public partial class UserPayouts
|
||||
{
|
||||
var request = new GetUserCommissionPayoutsRequest
|
||||
{
|
||||
PageIndex = state.Page + 1,
|
||||
PageSize = state.PageSize
|
||||
PaginationState = new PaginationState()
|
||||
{
|
||||
PageNumber = state.Page + 1,
|
||||
PageSize = state.PageSize
|
||||
},
|
||||
Filter = new GetUserPayoutsFilter()
|
||||
};
|
||||
|
||||
if (_filterUserId.HasValue)
|
||||
if (_filterUserId.HasValue && _filterUserId.Value > 0)
|
||||
{
|
||||
request.UserId = _filterUserId.Value;
|
||||
request.Filter.UserId = _filterUserId.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_filterWeekNumber))
|
||||
if (!string.IsNullOrWhiteSpace(_filterWeekNumber))
|
||||
{
|
||||
request.WeekNumber = _filterWeekNumber;
|
||||
request.Filter.WeekNumber = _filterWeekNumber;
|
||||
}
|
||||
|
||||
if (_filterStatus.HasValue)
|
||||
{
|
||||
request.Status = _filterStatus.Value;
|
||||
request.Filter.Status = _filterStatus.Value;
|
||||
}
|
||||
|
||||
var result = await CommissionContract.GetUserCommissionPayoutsAsync(request);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/commission/reports"
|
||||
|
||||
@using MudBlazor
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
@attribute [Authorize]
|
||||
|
||||
@using MudBlazor
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using Microsoft.JSInterop
|
||||
@using System.Text
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-1">گزارش برداشتها</MudText>
|
||||
@@ -41,10 +42,8 @@
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudNumericField TValue="long?" @bind-Value="_userId"
|
||||
Label="شناسه کاربر (اختیاری)"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" />
|
||||
<UserAutoComplete Label="جستجوی کاربر (اختیاری)"
|
||||
@bind-SelectedUserId="_userId" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@page "/commission/withdrawals"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">درخواستهای برداشت</MudText>
|
||||
@@ -15,14 +16,8 @@
|
||||
<MudText Typo="Typo.h6">درخواستهای برداشت</MudText>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudNumericField T="long?"
|
||||
HideSpinButtons="true"
|
||||
Clearable="true"
|
||||
Label="شناسه کاربر"
|
||||
@bind-Value="_filterUserId"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Style="max-width: 140px;" />
|
||||
<UserAutoComplete Label="جستجوی کاربر"
|
||||
@bind-SelectedUserId="_filterUserId" />
|
||||
<MudTextField T="string"
|
||||
Clearable="true"
|
||||
Label="شماره شبا (IBAN)"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using BackOffice.BFF.Commission.Protobuf;
|
||||
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using GrpcWithdrawalRequestModel = BackOffice.BFF.Commission.Protobuf.WithdrawalRequestModel;
|
||||
using GrpcGetWithdrawalRequestsRequest = BackOffice.BFF.Commission.Protobuf.GetWithdrawalRequestsRequest;
|
||||
using GrpcWithdrawalRequestModel = Foursat.BackOffice.BFF.Commission.Protos.WithdrawalRequestModel;
|
||||
using GrpcGetWithdrawalRequestsRequest = Foursat.BackOffice.BFF.Commission.Protos.GetWithdrawalRequestsRequest;
|
||||
|
||||
namespace BackOffice.Pages.Commission;
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
@page "/dashboard/overview"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
@@ -301,7 +300,7 @@
|
||||
var daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
|
||||
var firstMonday = jan1.AddDays(daysOffset);
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@page "/network/balances"
|
||||
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using MudBlazor
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">گزارش موجودیهای هفتگی</MudText>
|
||||
@@ -13,16 +14,17 @@
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudNumericField @bind-Value="_filterUserId"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
Min="0" />
|
||||
@* <MudNumericField @bind-Value="_filterUserId" *@
|
||||
@* Label="شناسه کاربر" *@
|
||||
@* Variant="Variant.Outlined" *@
|
||||
@* Min="0" /> *@
|
||||
<UserAutoComplete Label="جستجوی کاربر"
|
||||
@bind-SelectedUserId="_filterUserId" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudTextField @bind-Value="_filterWeekNumber"
|
||||
Label="شماره هفته"
|
||||
Placeholder="2025-W48"
|
||||
Variant="Variant.Outlined" />
|
||||
<WeekNumberPicker Label="شماره هفته"
|
||||
@bind-SelectedWeekNumber="_filterWeekNumber"
|
||||
CoerceText="true" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudNumericField @bind-Value="_minBalance"
|
||||
@@ -61,7 +63,7 @@
|
||||
</MudButton>
|
||||
</div>
|
||||
|
||||
<MudDataGrid T="UserWeeklyBalanceModel"
|
||||
<MudDataGrid T="UserWeeklyBalanceModel" @ref="_grid"
|
||||
ServerData="@(new Func<GridState<UserWeeklyBalanceModel>, Task<GridData<UserWeeklyBalanceModel>>>(ServerReload))"
|
||||
Filterable="true"
|
||||
Hover="true">
|
||||
@@ -149,7 +151,7 @@
|
||||
|
||||
@code {
|
||||
[Inject] public CommissionContract.CommissionContractClient CommissionClient { get; set; }
|
||||
|
||||
private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
||||
private long? _filterUserId = null;
|
||||
private string _filterWeekNumber = "";
|
||||
private int? _minBalance = null;
|
||||
@@ -221,7 +223,7 @@
|
||||
|
||||
private async Task ApplyFilter()
|
||||
{
|
||||
StateHasChanged();
|
||||
await _grid.ReloadServerData();
|
||||
}
|
||||
|
||||
private void CalculateTotals(List<UserWeeklyBalanceModel> items)
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
@page "/network/tree"
|
||||
@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>
|
||||
|
||||
<MudPaper Class="pa-4 mb-4">
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||
<MudNumericField @bind-Value="_searchUserId"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
Style="max-width: 200px;" />
|
||||
<div style="max-width: 300px; min-width: 250px;">
|
||||
<UserAutoComplete Label="جستجوی کاربر"
|
||||
@bind-SelectedUserId="_searchUserId" />
|
||||
</div>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="LoadTree"
|
||||
@@ -42,8 +44,13 @@
|
||||
}
|
||||
else if (_treeData != null && _treeData.Nodes.Any())
|
||||
{
|
||||
<MudPaper Class="pa-4">
|
||||
<MudDataGrid T="NetworkTreeNodeModel" Items="@_treeData.Nodes" Hover="true" Filterable="true">
|
||||
<MudPaper Class="pa-4" Style="min-height: 800px;">
|
||||
<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>
|
||||
<PropertyColumn Property="x => x.UserId" Title="شناسه کاربر" />
|
||||
<PropertyColumn Property="x => x.UserName" Title="نام کاربر" />
|
||||
@@ -62,17 +69,28 @@
|
||||
|
||||
<PropertyColumn Property="x => x.IsActive" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@(context.Item.IsActive ? "فعال" : "غیرفعال")
|
||||
@if (context.Item.IsActive!=null)
|
||||
{
|
||||
|
||||
<MudChip T="string"
|
||||
Color="@((bool)context.Item.IsActive ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@((bool)context.Item.IsActive ? "فعال" : "غیرفعال")
|
||||
</MudChip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.JoinedAt" Title="تاریخ عضویت">
|
||||
<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>
|
||||
</PropertyColumn>
|
||||
|
||||
@@ -104,41 +122,84 @@
|
||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||
|
||||
private long _searchUserId;
|
||||
private long? _searchUserId;
|
||||
private GetNetworkTreeResponse _treeData;
|
||||
private bool _isLoading;
|
||||
private int _totalMembers;
|
||||
private int _leftCount;
|
||||
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()
|
||||
{
|
||||
if (_searchUserId <= 0)
|
||||
if (!_searchUserId.HasValue || _searchUserId.Value <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً شناسه کاربر را وارد کنید", Severity.Warning);
|
||||
Snackbar.Add("لطفاً کاربر را انتخاب کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
StateHasChanged(); // Force render to show loading state
|
||||
|
||||
try
|
||||
{
|
||||
var request = new GetNetworkTreeRequest { RootUserId = _searchUserId };
|
||||
var request = new GetNetworkTreeRequest { UserId = _searchUserId.Value, MaxDepth = 20 };
|
||||
_treeData = await NetworkContract.GetNetworkTreeAsync(request);
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری درخت: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_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()
|
||||
{
|
||||
if (_treeData == null || !_treeData.Nodes.Any()) return;
|
||||
@@ -152,4 +213,9 @@
|
||||
{
|
||||
NavigationManager.NavigateTo($"/network/user-info/{userId}");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dotNetRef?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/network/statistics"
|
||||
|
||||
@using Foursat.BackOffice.BFF.NetworkMembership.Protos
|
||||
@using MudBlazor
|
||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">آمار شبکه</MudText>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@page "/network/user-info/{UserId:long}"
|
||||
@using Foursat.BackOffice.BFF.NetworkMembership.Protos
|
||||
@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>
|
||||
|
||||
@if (_isLoading)
|
||||
@@ -15,6 +15,7 @@
|
||||
else if (_userInfo != null)
|
||||
{
|
||||
<MudGrid>
|
||||
<!-- اطلاعات کاربر -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
@@ -33,6 +34,34 @@
|
||||
<td><strong>نام کاربر:</strong></td>
|
||||
<td>@_userInfo.UserName</td>
|
||||
</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>
|
||||
<td><strong>موقعیت:</strong></td>
|
||||
<td>
|
||||
@@ -43,13 +72,9 @@
|
||||
</MudChip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>عمق در درخت:</strong></td>
|
||||
<td>@_userInfo.NetworkLevel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
@@ -57,6 +82,7 @@
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<!-- ساختار شبکه -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
@@ -67,17 +93,20 @@
|
||||
<MudCardContent>
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
@if (_userInfo.ParentId.HasValue && _userInfo.ParentId.Value > 0)
|
||||
@if (_userInfo.ParentId.HasValue && _userInfo.ParentId.Value > 0)
|
||||
{
|
||||
<tr>
|
||||
<td><strong>والد:</strong></td>
|
||||
<td>
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.ParentId))">
|
||||
@_userInfo.ParentName (ID: @_userInfo.ParentId)
|
||||
</MudButton>
|
||||
<MudStack Spacing="1">
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.ParentId))">
|
||||
@_userInfo.ParentName (ID: @_userInfo.ParentId)
|
||||
</MudButton>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">@_userInfo.ParentMobile</MudText>
|
||||
</MudStack>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -86,12 +115,21 @@
|
||||
<td>
|
||||
@if (_userInfo.LeftChildId > 0)
|
||||
{
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Success"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.LeftChildId))">
|
||||
@_userInfo.LeftChildName (ID: @_userInfo.LeftChildId)
|
||||
</MudButton>
|
||||
<MudStack Spacing="1">
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Success"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.LeftChildId))">
|
||||
@_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
|
||||
{
|
||||
@@ -104,12 +142,21 @@
|
||||
<td>
|
||||
@if (_userInfo.RightChildId > 0)
|
||||
{
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Warning"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.RightChildId))">
|
||||
@_userInfo.RightChildName (ID: @_userInfo.RightChildId)
|
||||
</MudButton>
|
||||
<MudStack Spacing="1">
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Warning"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.RightChildId))">
|
||||
@_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
|
||||
{
|
||||
@@ -123,21 +170,183 @@
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<!-- آمار شبکه -->
|
||||
<MudItem xs="12">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">آمار شبکه</MudText>
|
||||
<MudText Typo="Typo.h6">آمار کامل شبکه</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudAlert Severity="Severity.Info">
|
||||
<MudText>آمار تجمعی شبکه در نسخه بعدی اضافه خواهد شد</MudText>
|
||||
</MudAlert>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<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>
|
||||
</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>
|
||||
<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">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
@@ -155,9 +364,15 @@
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Info"
|
||||
StartIcon="@Icons.Material.Filled.History"
|
||||
OnClick="ViewHistory">
|
||||
تاریخچه تغییرات
|
||||
StartIcon="@Icons.Material.Filled.Money"
|
||||
OnClick="@(() => NavigationManager.NavigateTo($"/commission/payouts?userId={UserId}"))">
|
||||
Payout های کاربر
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
StartIcon="@Icons.Material.Filled.Refresh"
|
||||
OnClick="LoadUserInfo">
|
||||
بروزرسانی
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
@@ -177,6 +392,7 @@
|
||||
[Parameter] public long UserId { get; set; }
|
||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||
[Inject] public BackOffice.Services.IPersianDateTimeService PersianDateTime { get; set; }
|
||||
|
||||
private GetUserNetworkResponse _userInfo;
|
||||
private bool _isLoading = true;
|
||||
@@ -217,10 +433,15 @@
|
||||
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.)
|
||||
// Requires: Historical tracking table in CMS database
|
||||
Snackbar.Add("این قابلیت به زودی اضافه خواهد شد", Severity.Info);
|
||||
return method switch
|
||||
{
|
||||
0 => "خریداری نشده",
|
||||
1 => "خرید با پول",
|
||||
2 => "خرید با بالانس",
|
||||
3 => "هدیه سیستم",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
@using BackOffice.BFF.ManualPayment.Protobuf
|
||||
@using BackOffice.Pages.Payment.Components
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
<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" />
|
||||
<UserAutoComplete Label="جستجوی کاربر"
|
||||
@bind-SelectedUserId="_userIdFilter" />
|
||||
|
||||
<MudTextField T="string"
|
||||
Label="شماره مرجع"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/system/configuration"
|
||||
@attribute [Authorize(Roles = "Administrator")]
|
||||
|
||||
@using Foursat.BackOffice.BFF.Configuration.Protobuf
|
||||
@using Foursat.BackOffice.BFF.Configuration.Protos
|
||||
@using MudBlazor
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
@using MudBlazor
|
||||
@using BackOffice.Pages.SystemManagement
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">کنترل Worker محاسبات</MudText>
|
||||
@@ -31,11 +32,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>آخرین اجرا:</strong></td>
|
||||
<td>@_lastRunTime.ToString("yyyy/MM/dd HH:mm:ss")</td>
|
||||
<td>@PersianDateTime.ConvertToPersianDateTime(_lastRunTime)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>اجرای بعدی:</strong></td>
|
||||
<td>@_nextRunTime.ToString("yyyy/MM/dd HH:mm:ss")</td>
|
||||
<td>@PersianDateTime.ConvertToPersianDateTime(_nextRunTime)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تعداد اجراهای موفق:</strong></td>
|
||||
@@ -59,10 +60,9 @@
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_manualWeekNumber"
|
||||
Label="شماره هفته برای اجرای دستی"
|
||||
Placeholder="2025-W48"
|
||||
Variant="Variant.Outlined" />
|
||||
<WeekNumberPicker Label="شماره هفته برای اجرای دستی"
|
||||
@bind-SelectedWeekNumber="_manualWeekNumber"
|
||||
CoerceText="true" />
|
||||
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
@@ -143,8 +143,8 @@
|
||||
<MudTh>پیام خطا</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="زمان">@context.ExecutionTime.ToString("yyyy/MM/dd HH:mm:ss")</MudTd>
|
||||
<MudTd DataLabel="هفته">@context.WeekNumber</MudTd>
|
||||
<MudTd DataLabel="زمان">@PersianDateTime.ConvertToPersianDateTime(context.ExecutionTime)</MudTd>
|
||||
<MudTd DataLabel="هفته">@PersianDateTime.ConvertWeekNumberToPersian(context.WeekNumber)</MudTd>
|
||||
<MudTd DataLabel="وضعیت">
|
||||
<MudChip T="string"
|
||||
Color="@(context.Status == "موفق" ? Color.Success : Color.Error)"
|
||||
@@ -176,7 +176,8 @@
|
||||
</MudContainer>
|
||||
|
||||
@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 DateTime _lastRunTime = DateTime.Now.AddHours(-2);
|
||||
@@ -191,7 +192,7 @@
|
||||
{
|
||||
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;
|
||||
_lastRunTime = statusResponse.LastRunAt?.ToDateTime() ?? DateTime.MinValue;
|
||||
_nextRunTime = statusResponse.NextScheduledRun?.ToDateTime() ?? DateTime.MinValue;
|
||||
@@ -217,7 +218,7 @@
|
||||
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"اجرای دستی محاسبات",
|
||||
$"آیا از اجرای محاسبات برای هفته {_manualWeekNumber} اطمینان دارید؟",
|
||||
$"آیا از اجرای محاسبات برای هفته {PersianDateTime.ConvertWeekNumberToPersian(_manualWeekNumber)} اطمینان دارید؟",
|
||||
yesText: "بله، اجرا شود", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
@@ -225,13 +226,14 @@
|
||||
_isProcessing = true;
|
||||
try
|
||||
{
|
||||
var request = new BackOffice.BFF.Commission.Protobuf.TriggerWeeklyCalculationRequest
|
||||
var request = new Foursat.BackOffice.BFF.Commission.Protos.TriggerWeeklyCalculationRequest
|
||||
{
|
||||
WeekNumber = _manualWeekNumber
|
||||
};
|
||||
await CommissionClient.TriggerWeeklyCalculationAsync(request);
|
||||
|
||||
Snackbar.Add($"محاسبات هفته {_manualWeekNumber} با موفقیت آغاز شد", Severity.Success);
|
||||
var persianWeek = PersianDateTime.ConvertWeekNumberToPersian(_manualWeekNumber);
|
||||
Snackbar.Add($"محاسبات هفته {persianWeek} با موفقیت آغاز شد", Severity.Success);
|
||||
_manualWeekNumber = "";
|
||||
await RefreshLog();
|
||||
}
|
||||
@@ -271,7 +273,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new BackOffice.BFF.Commission.Protobuf.GetWorkerExecutionLogsRequest
|
||||
var request = new Foursat.BackOffice.BFF.Commission.Protos.GetWorkerExecutionLogsRequest
|
||||
{
|
||||
PageIndex = 1,
|
||||
PageSize = 20
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder
|
||||
@using BackOffice.Pages.UserOrder.Components
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using DataModel = BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder.GetAllUserOrderByFilterResponseModel
|
||||
|
||||
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared"
|
||||
@@ -17,13 +18,8 @@
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"/>
|
||||
|
||||
<MudNumericField HideSpinButtons="true"
|
||||
T="long?"
|
||||
Clearable="true"
|
||||
Label="شناسه کاربر"
|
||||
@bind-Value="@_userIdFilter"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"/>
|
||||
<UserAutoComplete Label="جستجوی کاربر"
|
||||
@bind-SelectedUserId="@_userIdFilter" />
|
||||
|
||||
<MudNumericField HideSpinButtons="true"
|
||||
T="long?"
|
||||
|
||||
@@ -29,27 +29,27 @@ public class AuthorizationService : IAuthorizationService
|
||||
if (cachedPermissions == null || cachedPermissions.Count == 0)
|
||||
{
|
||||
// فعلاً بر اساس Role ساده تصمیم میگیریم تا زمانی که BFF Permission API آماده شود
|
||||
var role = await GetUserRoleAsync();
|
||||
if (string.IsNullOrWhiteSpace(role))
|
||||
var roles = await GetUserRolesAsync();
|
||||
if (roles == null || roles.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// SuperAdmin: همه دسترسیها
|
||||
if (string.Equals(role, "SuperAdmin", StringComparison.OrdinalIgnoreCase))
|
||||
if (roles.Any(r => string.Equals(r, "Administrator", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Admin: اجازه دسترسی به بیشتر صفحات مدیریتی
|
||||
if (string.Equals(role, "Admin", StringComparison.OrdinalIgnoreCase))
|
||||
if (roles.Any(r => string.Equals(r, "Admin", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// فعلاً همه permissionهای UI را برای Admin آزاد میکنیم
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
@@ -61,6 +61,12 @@ public class AuthorizationService : IAuthorizationService
|
||||
}
|
||||
|
||||
public async Task<string?> GetUserRoleAsync()
|
||||
{
|
||||
var roles = await GetUserRolesAsync();
|
||||
return roles?.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<List<string>?> GetUserRolesAsync()
|
||||
{
|
||||
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
var user = authState.User;
|
||||
@@ -70,8 +76,8 @@ public class AuthorizationService : IAuthorizationService
|
||||
return null;
|
||||
}
|
||||
|
||||
var roleClaim = user.FindFirst(ClaimTypes.Role) ?? user.FindFirst("role");
|
||||
return roleClaim?.Value;
|
||||
var roleClaims = user.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -98,11 +98,37 @@
|
||||
|
||||
@if (CanManageProducts)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Products)"
|
||||
Icon="@Icons.Material.Filled.ShoppingBag">
|
||||
مدیریت محصول
|
||||
</MudNavLink>
|
||||
<MudNavGroup Title="مدیریت محصولات" Icon="@Icons.Material.Filled.ShoppingBag" Expanded="false">
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Products)"
|
||||
Icon="@Icons.Material.Filled.Inventory">
|
||||
لیست محصولات
|
||||
</MudNavLink>
|
||||
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/products/bulk-edit"
|
||||
Icon="@Icons.Material.Filled.EditNote">
|
||||
ویرایش دستهجمعی
|
||||
</MudNavLink>
|
||||
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Category)"
|
||||
Icon="@Icons.Material.Filled.Category">
|
||||
دستهبندی محصولات
|
||||
</MudNavLink>
|
||||
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/products/categories-dragdrop"
|
||||
Icon="@Icons.Material.Filled.DragIndicator">
|
||||
ترتیب دستهبندیها
|
||||
</MudNavLink>
|
||||
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/tags"
|
||||
Icon="@Icons.Material.Filled.Label">
|
||||
مدیریت تگها
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
}
|
||||
|
||||
@if (CanViewOrders)
|
||||
@@ -114,24 +140,6 @@
|
||||
</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"
|
||||
@@ -149,6 +157,22 @@
|
||||
مدیریت نقش
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanManagePayments)
|
||||
{
|
||||
<MudNavGroup Title="پرداختها" Icon="@Icons.Material.Filled.Payment" Expanded="false">
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/payment/manual-payments"
|
||||
Icon="@Icons.Material.Filled.Payments">
|
||||
پرداختهای دستی
|
||||
</MudNavLink>
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/payment/membership"
|
||||
Icon="@Icons.Material.Filled.CardMembership">
|
||||
پرداخت دستی عضویت
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
}
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@@ -166,7 +190,7 @@
|
||||
محصولات تخفیفی
|
||||
</MudNavLink>
|
||||
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/discount-categories"
|
||||
Icon="@Icons.Material.Filled.Category">
|
||||
دستهبندیهای فروشگاه
|
||||
@@ -265,6 +289,7 @@
|
||||
private bool CanManageTags;
|
||||
private bool CanManageUsers;
|
||||
private bool CanManageRoles;
|
||||
private bool CanManagePayments;
|
||||
private bool CanManageDiscountShop;
|
||||
private bool CanManagePublicMessages;
|
||||
private bool CanViewSystemAlerts;
|
||||
@@ -292,6 +317,7 @@
|
||||
CanManageTags = await AuthorizationService.HasPermissionAsync("tags.manage");
|
||||
CanManageUsers = await AuthorizationService.HasPermissionAsync("users.view");
|
||||
CanManageRoles = await AuthorizationService.HasPermissionAsync("roles.manage");
|
||||
CanManagePayments = await AuthorizationService.HasPermissionAsync("manualpayments.create");
|
||||
CanManageDiscountShop = await AuthorizationService.HasPermissionAsync("discountshop.manage");
|
||||
CanManagePublicMessages = await AuthorizationService.HasPermissionAsync("publicmessages.view");
|
||||
CanViewSystemAlerts = await AuthorizationService.HasPermissionAsync("system.alerts.view");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
// "GwUrl": "https://bogw.kbs1.ir",
|
||||
"GwUrl": "https://backoffice-bff.foursat.afrino.co",
|
||||
// "GwUrl": "https://localhost:6468",
|
||||
"Authentication": {
|
||||
//"Authority": "https://localhost:5001",
|
||||
"Authority": "https://ids.afrino.co/",
|
||||
|
||||
@@ -34,8 +34,10 @@
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<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/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="_framework/blazor.webassembly.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