Files
FrontOffice/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor.cs
T
masoodafar-web 8252e408b6
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 3m41s
feat(organization-chart): update registration date handling and styling
- Modified the OrganizationChart component to convert and display the registration date in Jalali format, enhancing user understanding of the date information.
- Updated the NetworkMembershipDtos class to include a comment in Persian for the JoinedAt property, clarifying its purpose.
- Adjusted the NetworkMembershipService to utilize the UserCreated date when available, improving data accuracy.
- Introduced new CSS styles for the registration date display, ensuring better visibility and presentation in the organization chart.

These changes enhance the clarity and usability of the organization chart by providing accurate registration date information in a user-friendly format.
2026-09-04 06:48:24 +03:30

281 lines
7.4 KiB
C#

using DateTimeConverterCL;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace FrontOffice.Main.Pages.Profile.Components;
public partial class OrganizationChart : IAsyncDisposable
{
private NetworkTreeDto? _networkTree;
private NetworkStatisticsDto? _statistics;
private bool _isLoading = true;
private bool _hasError = false;
private int _selectedDepthValue = 3;
private DotNetObjectReference<OrganizationChart>? _dotNetHelper;
private bool _chartNeedsInit = false;
private bool _jsReady = false;
// برای نگهداری شناسه کاربر فعلی که درختش نمایش داده شده
private long? _currentViewUserId = null;
// Stack برای بازگشت به عقب
private Stack<long> _navigationHistory = new();
private int _selectedDepth
{
get => _selectedDepthValue;
set
{
if (_selectedDepthValue != value)
{
_selectedDepthValue = value;
_ = OnDepthChanged();
}
}
}
[Inject] private NetworkMembershipService NetworkService { get; set; } = default!;
// JSRuntime is injected globally via _Imports.razor;
protected override async Task OnInitializedAsync()
{
await LoadData();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_jsReady = true;
}
// Initialize chart after JS is ready and data is loaded
if (_jsReady && _chartNeedsInit && _networkTree?.RootNode != null)
{
_chartNeedsInit = false;
await InitializeChart();
}
}
private async Task LoadData()
{
_isLoading = true;
_hasError = false;
StateHasChanged();
try
{
// Load tree and statistics in parallel
var treeTask = NetworkService.GetMyNetworkTreeAsync(_selectedDepth);
var statsTask = NetworkService.GetMyNetworkStatisticsAsync();
await Task.WhenAll(treeTask, statsTask);
_networkTree = await treeTask;
_statistics = await statsTask;
// Mark that chart needs to be initialized
_chartNeedsInit = true;
}
catch (Exception ex)
{
Console.WriteLine($"Error loading network data: {ex.Message}");
_hasError = true;
}
finally
{
_isLoading = false;
StateHasChanged();
}
}
private async Task InitializeChart()
{
if (_networkTree?.RootNode == null) return;
try
{
_dotNetHelper = DotNetObjectReference.Create(this);
// Explicit camelCase mapping (same pattern as BackOffice admin chart)
var flatData = _networkTree.ToFlatArray().Select(n => new
{
id = n.Id,
parentId = n.ParentId,
fullName = n.FullName,
mobile = n.Mobile,
avatar = n.Avatar,
position = n.Position,
level = n.Level,
isActive = n.IsActive,
isClubActive = n.IsClubActive,
activationWeekNumber = n.ActivationWeekNumber,
registeredAt = n.JoinedAt.HasValue
? n.JoinedAt.Value.ToLocalTime().MiladiToJalali()
: "",
referralCode = n.ReferralCode,
packageName = n.PackageName ?? string.Empty
}).ToArray();
await JSRuntime.InvokeVoidAsync("OrgChart.init", "org-chart-container", flatData, _dotNetHelper);
}
catch (Exception ex)
{
Console.WriteLine($"Error initializing chart: {ex.Message}");
}
}
private async Task RefreshData()
{
await LoadData();
if (_networkTree?.RootNode != null)
{
await InitializeChart();
}
}
private async Task OnDepthChanged()
{
await RefreshData();
}
private async Task ExpandAll()
{
try
{
await JSRuntime.InvokeVoidAsync("OrgChart.expandAll");
}
catch { }
}
private async Task CollapseAll()
{
try
{
await JSRuntime.InvokeVoidAsync("OrgChart.collapseAll");
}
catch { }
}
private async Task CenterChart()
{
try
{
await JSRuntime.InvokeVoidAsync("OrgChart.center");
}
catch { }
}
private async Task FitToScreen()
{
try
{
await JSRuntime.InvokeVoidAsync("OrgChart.fitToScreen");
}
catch { }
}
private async Task ExportPng()
{
try
{
await JSRuntime.InvokeVoidAsync("OrgChart.exportPng");
}
catch { }
}
/// <summary>
/// Called from JavaScript when a node is clicked
/// </summary>
[JSInvokable]
public async Task OnNodeClicked(long userId)
{
// اگر روی همان نود کلیک شده، کاری نکن
if (_currentViewUserId == userId) return;
// ذخیره نود فعلی در history برای بازگشت
if (_currentViewUserId.HasValue)
{
_navigationHistory.Push(_currentViewUserId.Value);
}
await LoadSubordinateTree(userId);
}
/// <summary>
/// بارگذاری درخت یک زیرمجموعه
/// </summary>
private async Task LoadSubordinateTree(long targetUserId)
{
_isLoading = true;
StateHasChanged();
try
{
_networkTree = await NetworkService.GetSubordinateTreeAsync(targetUserId, _selectedDepth);
_currentViewUserId = targetUserId;
_chartNeedsInit = true;
}
catch (UnauthorizedAccessException)
{
// نمایش پیام خطا - این کاربر زیرمجموعه نیست
Console.WriteLine("Unauthorized access to subordinate tree");
}
catch (Exception ex)
{
Console.WriteLine($"Error loading subordinate tree: {ex.Message}");
}
finally
{
_isLoading = false;
StateHasChanged();
}
}
/// <summary>
/// بازگشت به درخت قبلی
/// </summary>
private async Task GoBack()
{
if (_navigationHistory.Count > 0)
{
var previousUserId = _navigationHistory.Pop();
await LoadSubordinateTree(previousUserId);
}
else
{
// بازگشت به درخت خود کاربر
_currentViewUserId = null;
await LoadData();
if (_networkTree?.RootNode != null)
{
await InitializeChart();
}
}
}
/// <summary>
/// بازگشت به درخت اصلی (خود کاربر)
/// </summary>
private async Task GoToMyTree()
{
_navigationHistory.Clear();
_currentViewUserId = null;
await LoadData();
if (_networkTree?.RootNode != null)
{
await InitializeChart();
}
}
public async ValueTask DisposeAsync()
{
try
{
await JSRuntime.InvokeVoidAsync("OrgChart.dispose");
}
catch { }
_dotNetHelper?.Dispose();
}
}