feat: integrate d3-org-chart for network visualization and add SignalR token notification service
This commit is contained in:
@@ -21,7 +21,7 @@ public partial class Index:IDisposable
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
};
|
||||
await LoadPackagesAsync();
|
||||
// await LoadPackagesAsync();
|
||||
|
||||
//string mobileNumber = "09387342688";
|
||||
|
||||
@@ -50,37 +50,37 @@ public partial class Index:IDisposable
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
private async Task LoadPackagesAsync()
|
||||
{
|
||||
_isLoadingPackages = true;
|
||||
try
|
||||
{
|
||||
var response = await PackageClient.GetAllPackageByFilterAsync(request: new());
|
||||
if (response?.Models?.Any() == true)
|
||||
{
|
||||
_packs = response.Models.Select(p => new Pack(
|
||||
Id: p.Id,
|
||||
Title: p.Title,
|
||||
Body: p.Description,
|
||||
Image: UrlUtility.DownloadUrl + p.ImagePath,
|
||||
Price: p.Price
|
||||
)).ToList();
|
||||
}
|
||||
else
|
||||
_packs = new List<Pack>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری پکیجها: {ex.Message}", Severity.Error);
|
||||
// Fallback to empty list
|
||||
_packs = new List<Pack>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoadingPackages = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
// private async Task LoadPackagesAsync()
|
||||
// {
|
||||
// _isLoadingPackages = true;
|
||||
// try
|
||||
// {
|
||||
// var response = await PackageClient.GetAllPackageByFilterAsync(request: new());
|
||||
// if (response?.Models?.Any() == true)
|
||||
// {
|
||||
// _packs = response.Models.Select(p => new Pack(
|
||||
// Id: p.Id,
|
||||
// Title: p.Title,
|
||||
// Body: p.Description,
|
||||
// Image: UrlUtility.DownloadUrl + p.ImagePath,
|
||||
// Price: p.Price
|
||||
// )).ToList();
|
||||
// }
|
||||
// else
|
||||
// _packs = new List<Pack>();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Snackbar.Add($"خطا در بارگذاری پکیجها: {ex.Message}", Severity.Error);
|
||||
// // Fallback to empty list
|
||||
// _packs = new List<Pack>();
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// _isLoadingPackages = false;
|
||||
// await InvokeAsync(StateHasChanged);
|
||||
// }
|
||||
// }
|
||||
|
||||
private void JoinWaitlist()
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ public partial class AddAddressDialog : ComponentBase
|
||||
var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest
|
||||
{
|
||||
PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 20 },
|
||||
Filter = new CityProto.GetAllCitiesByFilterFilter { Native = value }
|
||||
Filter = new CityProto.GetAllCitiesByFilterFilter { Name = value }
|
||||
}, cancellationToken: ct);
|
||||
|
||||
return response?.Models?.ToList() ?? new List<CityProto.GetAllCitiesByFilterResponseModel>();
|
||||
|
||||
@@ -1,62 +1,82 @@
|
||||
@if (_currentUser != null)
|
||||
{
|
||||
<div class="org-chart-container">
|
||||
<div class="org-tree">
|
||||
<div class="org-node root-node" id="root-node">
|
||||
<div class="node-card">
|
||||
<div class="node-avatar">
|
||||
@if (!string.IsNullOrWhiteSpace(_currentUser.Avatar))
|
||||
{
|
||||
<MudAvatar Size="Size.Large">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="@_currentUser.Avatar" />
|
||||
</MudAvatar>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAvatar Size="Size.Large" Color="Color.Primary" Variant="Variant.Outlined">
|
||||
@(string.IsNullOrWhiteSpace(_currentUser.FirstName) ? "N" : _currentUser.FirstName.Substring(0, 1))
|
||||
</MudAvatar>
|
||||
}
|
||||
@implements IAsyncDisposable
|
||||
|
||||
@if (_currentUser?.Children?.Any() == true)
|
||||
{
|
||||
<button class="expand-btn" @onclick="ToggleExpand" data-user-id="@_currentUser.Id">
|
||||
<MudIcon Icon="@(_isExpanded ? Icons.Material.Filled.Remove : Icons.Material.Filled.Add)" Size="Size.Small" />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="node-info">
|
||||
@if (!string.IsNullOrWhiteSpace(_currentUser.FirstName) || !string.IsNullOrWhiteSpace(_currentUser.LastName))
|
||||
{
|
||||
<div class="node-name">@_currentUser.FirstName @_currentUser.LastName</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="node-name">@_currentUser.Mobile</div>
|
||||
}
|
||||
@* <div class="node-amounts">
|
||||
<div class="personal-amount">
|
||||
<span class="label">خرید شخصی:</span>
|
||||
<span class="amount">@(_currentUser?.PersonalPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
|
||||
</div>
|
||||
<div class="team-amount">
|
||||
<span class="label">خرید تیمی:</span>
|
||||
<span class="amount">@(_currentUser?.TeamPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
|
||||
</div>
|
||||
</div> *@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (_isExpanded && _currentUser?.Children?.Any() == true)
|
||||
{
|
||||
<CascadingValue Value="this">
|
||||
<OrganizationChartLevel Nodes="_currentUser.Children" Level="1" />
|
||||
</CascadingValue>
|
||||
}
|
||||
</div>
|
||||
<div class="org-chart-wrapper">
|
||||
@* Toolbar *@
|
||||
<div class="org-chart-toolbar">
|
||||
|
||||
|
||||
<MudSelect T="int" @bind-Value="_selectedDepth" Label="عمق" Variant="Variant.Outlined"
|
||||
Dense="true" Margin="Margin.Dense" Style="width: 90px; min-width: 90px;">
|
||||
<MudSelectItem Value="2">2 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="3">3 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="4">4 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="5">5 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="6">6 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="15">همه</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
<MudButtonGroup Variant="Variant.Outlined" Size="Size.Small" OverrideStyles="false">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.UnfoldMore" OnClick="ExpandAll" Title="باز کردن همه" Size="Size.Small" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.UnfoldLess" OnClick="CollapseAll" Title="بستن همه" Size="Size.Small" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.CenterFocusStrong" OnClick="CenterChart" Title="مرکز" Size="Size.Small" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ZoomOutMap" OnClick="FitToScreen" Title="نمایش کامل" Size="Size.Small" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Refresh" OnClick="RefreshData" Color="Color.Primary" Title="بروزرسانی" Size="Size.Small" />
|
||||
</MudButtonGroup>
|
||||
@if (_currentViewUserId.HasValue)
|
||||
{
|
||||
<MudButtonGroup Variant="Variant.Outlined" Size="Size.Small" OverrideStyles="false">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowForward" OnClick="GoBack" Title="بازگشت" Size="Size.Small" Color="Color.Secondary" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Home" OnClick="GoToMyTree" Title="درخت من" Size="Size.Small" Color="Color.Secondary" />
|
||||
</MudButtonGroup>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Chart Container *@
|
||||
@if (_isLoading)
|
||||
{
|
||||
<div class="chart-loading">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||
<MudText Typo="Typo.body2" Class="mt-2">در حال بارگذاری...</MudText>
|
||||
</div>
|
||||
}
|
||||
else if (_hasError)
|
||||
{
|
||||
<div class="error-message">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Error" Color="Color.Error" Size="Size.Large" />
|
||||
<MudText Typo="Typo.body1" Class="mt-2">خطا در بارگذاری دادهها</MudText>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="RefreshData">تلاش مجدد</MudButton>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div id="org-chart-container" class="org-chart-container"></div>
|
||||
}
|
||||
|
||||
@* Stats Bar *@
|
||||
@if (_statistics != null && !_isLoading)
|
||||
{
|
||||
<div class="org-chart-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">کل اعضا:</span>
|
||||
<span class="stat-value">@_statistics.TotalMembers</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">پای چپ:</span>
|
||||
<span class="stat-value left">@_statistics.LeftLegCount</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">پای راست:</span>
|
||||
<span class="stat-value right">@_statistics.RightLegCount</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">عمق:</span>
|
||||
<span class="stat-value">@_statistics.TreeDepth</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">پای ضعیف:</span>
|
||||
<span class="stat-value">@(_statistics.WeakerLeg == "Left" ? "چپ" : "راست")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,120 +1,262 @@
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using Mapster;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using static FrontOffice.Main.Pages.Profile.Components.OrganizationChartLevel;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Profile.Components;
|
||||
public partial class OrganizationChart
|
||||
{
|
||||
private UserNode? _currentUser;
|
||||
private bool _isExpanded;
|
||||
|
||||
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
|
||||
private GetUserResponse _userProfile = new();
|
||||
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 LoadCurrentUser();
|
||||
await LoadData();
|
||||
}
|
||||
private async Task LoadUserProfile()
|
||||
|
||||
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
|
||||
{
|
||||
_userProfile = await UserContract.GetUserAsync(request: new());
|
||||
// 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)
|
||||
{
|
||||
// Handle the case when user is not authenticated or API fails
|
||||
_userProfile = new GetUserResponse();
|
||||
Console.WriteLine($"Error loading network data: {ex.Message}");
|
||||
_hasError = true;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
private async Task LoadCurrentUser()
|
||||
{
|
||||
await LoadUserProfile();
|
||||
|
||||
|
||||
// Mock data - replace with actual API call
|
||||
_currentUser = new UserNode
|
||||
finally
|
||||
{
|
||||
Id = _userProfile.Id,
|
||||
FirstName = _userProfile.FirstName,
|
||||
LastName = _userProfile.LastName,
|
||||
Mobile = _userProfile.Mobile,
|
||||
Avatar = _userProfile.AvatarPath,
|
||||
PersonalPurchase = 0,
|
||||
TeamPurchase = 0,
|
||||
Children = await GetUserChildren(userId: _userProfile.Id)
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<List<UserNode>?> GetUserChildren(long userId)
|
||||
{
|
||||
Console.WriteLine("OK0");
|
||||
if (userId != 0)
|
||||
{
|
||||
Console.WriteLine("OK1");
|
||||
var children = await UserContract.GetAllUserByFilterAsync(request: new()
|
||||
{
|
||||
Filter = new()
|
||||
{
|
||||
ParentId = userId,
|
||||
}
|
||||
});
|
||||
if (children?.Models?.Any() == true)
|
||||
{
|
||||
Console.WriteLine("OK2");
|
||||
var result = new List<UserNode>();
|
||||
foreach (var item in children.Models)
|
||||
{
|
||||
var node = new UserNode
|
||||
{
|
||||
Id = item.Id,
|
||||
FirstName = item.FirstName,
|
||||
LastName = item.LastName,
|
||||
Mobile = item.Mobile,
|
||||
Avatar = item.AvatarPath,
|
||||
ReferralCode = item.ReferralCode,
|
||||
PersonalPurchase = 0,
|
||||
TeamPurchase = 0,
|
||||
Children = await GetUserChildren(userId: item.Id)
|
||||
};
|
||||
result.Add(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private void ToggleExpand()
|
||||
{
|
||||
_isExpanded = !_isExpanded;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public void ToggleNodeExpand(long userId)
|
||||
{
|
||||
var node = FindNode(_currentUser, userId);
|
||||
if (node != null)
|
||||
{
|
||||
node.IsExpanded = !node.IsExpanded;
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private UserNode? FindNode(UserNode? node, long userId)
|
||||
private async Task InitializeChart()
|
||||
{
|
||||
if (node == null) return null;
|
||||
if (node.Id == userId) return node;
|
||||
if (_networkTree?.RootNode == null) return;
|
||||
|
||||
if (node.Children != null)
|
||||
try
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
_dotNetHelper = DotNetObjectReference.Create(this);
|
||||
var flatData = _networkTree.ToFlatArray();
|
||||
|
||||
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)
|
||||
{
|
||||
var found = FindNode(child, userId);
|
||||
if (found != null) return found;
|
||||
await InitializeChart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,12 @@ using MudBlazor;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Profile.Components;
|
||||
|
||||
/// <summary>
|
||||
/// DEPRECATED: This component is no longer used.
|
||||
/// d3-org-chart is now used for tree visualization.
|
||||
/// Kept for backward compatibility.
|
||||
/// </summary>
|
||||
public partial class OrganizationChartLevel
|
||||
{
|
||||
[Parameter] public List<UserNode>? Nodes { get; set; }
|
||||
@@ -25,18 +31,12 @@ public partial class OrganizationChartLevel
|
||||
|
||||
private void ToggleNodeExpand(long userId)
|
||||
{
|
||||
if (ParentChart != null)
|
||||
{
|
||||
ParentChart.ToggleNodeExpand(userId);
|
||||
}
|
||||
// No longer needed - d3-org-chart handles this internally
|
||||
}
|
||||
|
||||
private void SafeToggleNodeExpand(long userId)
|
||||
{
|
||||
if (ParentChart != null)
|
||||
{
|
||||
ParentChart.ToggleNodeExpand(userId);
|
||||
}
|
||||
// No longer needed - d3-org-chart handles this internally
|
||||
}
|
||||
|
||||
private async Task CopyReferralCode(string referralCode)
|
||||
|
||||
@@ -56,6 +56,9 @@ public partial class Index
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
if (firstRender)
|
||||
{
|
||||
// Refresh token to get latest user claims
|
||||
await AuthService.RefreshTokenAsync();
|
||||
|
||||
await LoadUserAuthInfo();
|
||||
await LoadUserProfile();
|
||||
await LoadAddresses();
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<!-- Ensure latest MudBlazor CSS is used (cache-busting) -->
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" asp-append-version="true" />
|
||||
<!-- d3-org-chart custom styles -->
|
||||
<link href="css/org-chart.css" rel="stylesheet" asp-append-version="true" />
|
||||
</head>
|
||||
<body>
|
||||
<component type="typeof(App)" render-mode="Server" />
|
||||
@@ -34,6 +36,13 @@
|
||||
|
||||
<!-- Load MudBlazor JS before Blazor to avoid early JS interop calls failing; add cache-busting -->
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js" asp-append-version="true"></script>
|
||||
|
||||
<!-- d3.js and d3-org-chart for network tree visualization -->
|
||||
<script src="js/d3.v7.min.js"></script>
|
||||
<script src="js/d3-flextree.min.js"></script>
|
||||
<script src="js/d3-org-chart3.js"></script>
|
||||
<script src="js/org-chart.js" asp-append-version="true"></script>
|
||||
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
<script>
|
||||
window.fetchAndDownloadPdf = async function(html, fileName){
|
||||
|
||||
Reference in New Issue
Block a user