49017027d0
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 2m20s
- Blog management pages (posts, categories) - Content management pages (site pages, sections) - Contract management pages - Discount shop improvements (order details, product form, categories) - Wallet management pages - Commission withdrawal details/reject dialogs - Orders hub, sales reports - System hub, global search, breadcrumb - Club features page, club hub - UI/UX improvements across all pages - AppBreadcrumb, GlobalSearch components - NavMenu updates and styling
432 lines
17 KiB
Plaintext
432 lines
17 KiB
Plaintext
@page "/network/tree"
|
|
@attribute [Authorize]
|
|
@implements IAsyncDisposable
|
|
@inject IJSRuntime JS
|
|
|
|
@using CMSMicroservice.Protobuf.Protos.NetworkMembership
|
|
@using BackOffice.Pages.AutoComplete
|
|
|
|
<div>
|
|
<MudText Typo="Typo.h4" Class="mb-4">درخت شبکه</MudText>
|
|
|
|
@* Search & Filter Panel *@
|
|
<MudPaper Class="pa-4 mb-4">
|
|
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Class="mb-3">
|
|
<div style="max-width: 300px; min-width: 250px;">
|
|
<UserAutoComplete Label="جستجوی کاربر"
|
|
@bind-SelectedUserId="_searchUserId" />
|
|
</div>
|
|
<MudButton Variant="Variant.Filled"
|
|
Color="Color.Primary"
|
|
OnClick="LoadTree"
|
|
StartIcon="@Icons.Material.Filled.Search"
|
|
Disabled="_isLoading">
|
|
نمایش درخت
|
|
</MudButton>
|
|
</MudStack>
|
|
|
|
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
|
<MudSelect T="int" @bind-Value="_selectedDepth" Label="عمق نمایش" Variant="Variant.Outlined"
|
|
Dense="true" Margin="Margin.Dense" Style="width: 120px;">
|
|
<MudSelectItem T="int" Value="3">3 سطح</MudSelectItem>
|
|
<MudSelectItem T="int" Value="5">5 سطح</MudSelectItem>
|
|
<MudSelectItem T="int" Value="10">10 سطح</MudSelectItem>
|
|
<MudSelectItem T="int" Value="15">15 سطح</MudSelectItem>
|
|
<MudSelectItem T="int" Value="100">همه</MudSelectItem>
|
|
</MudSelect>
|
|
|
|
<MudSelect T="bool?" Label="وضعیت باشگاه" @bind-Value="_clubActiveFilter" Variant="Variant.Outlined"
|
|
Clearable="true" Dense="true" Style="width: 140px;">
|
|
<MudSelectItem T="bool?" Value="@(null)">همه</MudSelectItem>
|
|
<MudSelectItem T="bool?" Value="@(true)">فعال</MudSelectItem>
|
|
<MudSelectItem T="bool?" Value="@(false)">غیرفعال</MudSelectItem>
|
|
</MudSelect>
|
|
|
|
<WeekNumberPicker Label="هفته فعالسازی"
|
|
@bind-SelectedWeekDefinitionId="_activationWeekFilter" />
|
|
</MudStack>
|
|
</MudPaper>
|
|
|
|
@* Chart Toolbar *@
|
|
@if (_treeData != null && _treeData.Nodes.Any())
|
|
{
|
|
<MudPaper Class="pa-2 mb-2">
|
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
|
<MudButtonGroup Variant="Variant.Outlined" Size="Size.Small">
|
|
<MudIconButton Icon="@Icons.Material.Filled.UnfoldMore" OnClick="ExpandAll" Title="باز کردن همه" />
|
|
<MudIconButton Icon="@Icons.Material.Filled.UnfoldLess" OnClick="CollapseAll" Title="بستن همه" />
|
|
<MudIconButton Icon="@Icons.Material.Filled.CenterFocusStrong" OnClick="FitChart" Title="نمایش کامل" />
|
|
<MudIconButton Icon="@Icons.Material.Filled.Image" OnClick="ExportPng" Title="خروجی تصویر" />
|
|
</MudButtonGroup>
|
|
|
|
@if (_currentViewUserId.HasValue && _currentViewUserId != _searchUserId)
|
|
{
|
|
<MudButtonGroup Variant="Variant.Outlined" Size="Size.Small" Color="Color.Secondary">
|
|
<MudButton StartIcon="@Icons.Material.Filled.ArrowForward" OnClick="GoBack" Size="Size.Small">
|
|
بازگشت
|
|
</MudButton>
|
|
<MudButton StartIcon="@Icons.Material.Filled.Home" OnClick="GoToRoot" Size="Size.Small">
|
|
ریشه
|
|
</MudButton>
|
|
</MudButtonGroup>
|
|
}
|
|
|
|
<MudStack Row="true" Spacing="2">
|
|
<MudChip T="string" Color="Color.Info" Size="Size.Small" Icon="@Icons.Material.Filled.People">
|
|
کل: @_totalMembers
|
|
</MudChip>
|
|
<MudChip T="string" Color="Color.Success" Size="Size.Small">
|
|
چپ: @_leftCount
|
|
</MudChip>
|
|
<MudChip T="string" Color="Color.Warning" Size="Size.Small">
|
|
راست: @_rightCount
|
|
</MudChip>
|
|
</MudStack>
|
|
</MudStack>
|
|
</MudPaper>
|
|
}
|
|
|
|
@* Chart Container *@
|
|
@if (_isLoading)
|
|
{
|
|
<MudPaper Class="pa-8 d-flex flex-column align-center justify-center" Style="min-height: 500px;">
|
|
<MudProgressCircular Color="Color.Primary" Size="Size.Large" Indeterminate="true" />
|
|
<MudText Typo="Typo.body1" Class="mt-3">در حال بارگذاری درخت شبکه...</MudText>
|
|
</MudPaper>
|
|
}
|
|
else if (_hasError)
|
|
{
|
|
<MudPaper Class="pa-8 d-flex flex-column align-center justify-center" Style="min-height: 300px;">
|
|
<MudIcon Icon="@Icons.Material.Filled.Error" Color="Color.Error" Size="Size.Large" />
|
|
<MudText Typo="Typo.body1" Class="mt-2" Color="Color.Error">خطا در بارگذاری درخت</MudText>
|
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="LoadTree" Class="mt-2">
|
|
تلاش مجدد
|
|
</MudButton>
|
|
</MudPaper>
|
|
}
|
|
else if (_treeData != null && _treeData.Nodes.Any())
|
|
{
|
|
<MudPaper Class="pa-0" Style="min-height: 600px; overflow: hidden;">
|
|
<div id="admin-org-chart-container" class="admin-org-chart-container"></div>
|
|
</MudPaper>
|
|
|
|
@* Data Grid *@
|
|
<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"
|
|
SortMode="SortMode.Multiple" RowsPerPage="10">
|
|
<Columns>
|
|
<PropertyColumn Property="x => x.UserId" Title="شناسه" />
|
|
<PropertyColumn Property="x => x.UserName" Title="نام کاربر" />
|
|
|
|
<PropertyColumn Property="x => x.NetworkLeg" Title="موقعیت">
|
|
<CellTemplate>
|
|
<MudChip T="string"
|
|
Color="@(context.Item.NetworkLeg == 0 ? Color.Success : Color.Warning)"
|
|
Size="Size.Small">
|
|
@(context.Item.NetworkLeg == 0 ? "چپ" : "راست")
|
|
</MudChip>
|
|
</CellTemplate>
|
|
</PropertyColumn>
|
|
|
|
<PropertyColumn Property="x => x.NetworkLevel" Title="سطح" />
|
|
|
|
<PropertyColumn Property="x => x.IsClubActive" Title="باشگاه">
|
|
<CellTemplate>
|
|
<MudChip T="string"
|
|
Color="@(context.Item.IsClubActive ? Color.Success : Color.Default)"
|
|
Size="Size.Small">
|
|
@(context.Item.IsClubActive ? "فعال" : "غیرفعال")
|
|
</MudChip>
|
|
</CellTemplate>
|
|
</PropertyColumn>
|
|
|
|
<PropertyColumn Property="x => x.ClubActivatedAt" Title="تاریخ فعالسازی">
|
|
<CellTemplate>
|
|
@if (context.Item.ClubActivatedAt != null)
|
|
{
|
|
@context.Item.ClubActivatedAt.ToDateTime().ToLocalTime().MiladiToJalali()
|
|
}
|
|
else
|
|
{
|
|
<span style="color: #999;">-</span>
|
|
}
|
|
</CellTemplate>
|
|
</PropertyColumn>
|
|
|
|
<PropertyColumn Property="x => x.ActivationWeekDefinitionId" Title="هفته">
|
|
<CellTemplate>
|
|
@if (context.Item.ActivationWeekDefinitionId != null)
|
|
{
|
|
<MudChip T="string" Size="Size.Small" Color="Color.Info">
|
|
W@context.Item.ActivationWeekDefinitionId
|
|
</MudChip>
|
|
}
|
|
else
|
|
{
|
|
<span style="color: #999;">-</span>
|
|
}
|
|
</CellTemplate>
|
|
</PropertyColumn>
|
|
|
|
<TemplateColumn Title="عملیات" Sortable="false">
|
|
<CellTemplate>
|
|
<MudButtonGroup Size="Size.Small" Variant="Variant.Text">
|
|
<MudIconButton Icon="@Icons.Material.Filled.AccountTree"
|
|
Color="Color.Primary"
|
|
Title="نمایش درخت این کاربر"
|
|
OnClick="@(() => ViewUserTree(context.Item.UserId))" />
|
|
<MudIconButton Icon="@Icons.Material.Filled.Info"
|
|
Color="Color.Info"
|
|
Title="جزئیات کاربر"
|
|
OnClick="@(() => ViewUserDetails(context.Item.UserId))" />
|
|
</MudButtonGroup>
|
|
</CellTemplate>
|
|
</TemplateColumn>
|
|
</Columns>
|
|
<NoRecordsContent>
|
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
|
</NoRecordsContent>
|
|
<PagerContent>
|
|
<MudDataGridPager T="NetworkTreeNodeModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
|
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
|
RowsPerPageString="تعداد در صفحه" />
|
|
</PagerContent>
|
|
</MudDataGrid>
|
|
</MudPaper>
|
|
}
|
|
else
|
|
{
|
|
<MudPaper Class="pa-8">
|
|
<MudAlert Severity="Severity.Info" Icon="@Icons.Material.Filled.Info">
|
|
برای نمایش درخت شبکه، کاربر مورد نظر را جستجو کرده و دکمه "نمایش درخت" را بزنید.
|
|
</MudAlert>
|
|
</MudPaper>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
|
[Inject] public NavigationManager NavigationManager { get; set; }
|
|
|
|
private long? _searchUserId;
|
|
private long? _currentViewUserId;
|
|
private GetNetworkTreeResponse _treeData;
|
|
private bool _isLoading;
|
|
private bool _hasError;
|
|
private int _totalMembers;
|
|
private int _leftCount;
|
|
private int _rightCount;
|
|
private int _selectedDepth = 10;
|
|
private bool? _clubActiveFilter;
|
|
private long? _activationWeekFilter;
|
|
private DotNetObjectReference<NetworkTreeViewer> _dotNetRef;
|
|
private Stack<long> _navigationHistory = new();
|
|
private bool _chartNeedsInit = false;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
_dotNetRef = DotNetObjectReference.Create(this);
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (_chartNeedsInit && _treeData != null && _treeData.Nodes.Any())
|
|
{
|
|
_chartNeedsInit = false;
|
|
await RenderChart();
|
|
}
|
|
}
|
|
|
|
private async Task LoadTree()
|
|
{
|
|
if (!_searchUserId.HasValue || _searchUserId.Value <= 0)
|
|
{
|
|
Snackbar.Add("لطفاً کاربر را انتخاب کنید", Severity.Warning);
|
|
return;
|
|
}
|
|
|
|
_isLoading = true;
|
|
_hasError = false;
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
var request = new GetNetworkTreeRequest
|
|
{
|
|
UserId = _searchUserId.Value,
|
|
MaxDepth = _selectedDepth,
|
|
IsClubActive = _clubActiveFilter,
|
|
ActivationWeekDefinitionId = _activationWeekFilter?.ToString()
|
|
};
|
|
|
|
_treeData = await NetworkContract.GetNetworkTreeAsync(request);
|
|
_currentViewUserId = _searchUserId;
|
|
_navigationHistory.Clear();
|
|
|
|
CalculateStats();
|
|
|
|
_isLoading = false;
|
|
_chartNeedsInit = true;
|
|
StateHasChanged();
|
|
|
|
Snackbar.Add($"درخت بارگذاری شد - {_treeData.Nodes.Count} عضو", Severity.Success);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_hasError = true;
|
|
_isLoading = false;
|
|
Snackbar.Add($"خطا در بارگذاری درخت: {ex.Message}", Severity.Error);
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task LoadSubTree(long userId)
|
|
{
|
|
_isLoading = true;
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
var request = new GetNetworkTreeRequest
|
|
{
|
|
UserId = userId,
|
|
MaxDepth = _selectedDepth,
|
|
IsClubActive = _clubActiveFilter,
|
|
ActivationWeekDefinitionId = _activationWeekFilter?.ToString()
|
|
};
|
|
|
|
_treeData = await NetworkContract.GetNetworkTreeAsync(request);
|
|
|
|
if (_currentViewUserId.HasValue && _currentViewUserId != userId)
|
|
{
|
|
_navigationHistory.Push(_currentViewUserId.Value);
|
|
}
|
|
_currentViewUserId = userId;
|
|
|
|
CalculateStats();
|
|
|
|
_isLoading = false;
|
|
_chartNeedsInit = true;
|
|
StateHasChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_isLoading = false;
|
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task RenderChart()
|
|
{
|
|
if (_treeData == null || !_treeData.Nodes.Any()) return;
|
|
|
|
try
|
|
{
|
|
await Task.Delay(50); // Wait for DOM
|
|
|
|
var jsNodes = _treeData.Nodes.Select(n => new
|
|
{
|
|
id = n.UserId.ToString(),
|
|
parentId = n.ParentId > 0 ? n.ParentId.ToString() : "",
|
|
userId = n.UserId,
|
|
userName = n.UserName ?? $"کاربر {n.UserId}",
|
|
networkLevel = n.NetworkLevel,
|
|
networkLeg = n.NetworkLeg,
|
|
isClubActive = n.IsClubActive,
|
|
isActivatedInTargetWeek = n.IsActivatedInTargetWeek,
|
|
activationWeekDefinitionId = n.ActivationWeekDefinitionId,
|
|
clubActivatedAt = n.ClubActivatedAt?.ToDateTime().ToLocalTime().MiladiToJalali() ?? ""
|
|
}).ToArray();
|
|
|
|
var options = new { filterWeek = _activationWeekFilter };
|
|
|
|
await JS.InvokeVoidAsync("AdminOrgChart.init", "admin-org-chart-container", jsNodes, _dotNetRef, options);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
}
|
|
|
|
[JSInvokable]
|
|
public async Task OnNodeClicked(long userId)
|
|
{
|
|
if (userId == _currentViewUserId) return;
|
|
await LoadSubTree(userId);
|
|
}
|
|
|
|
private void CalculateStats()
|
|
{
|
|
if (_treeData == null || !_treeData.Nodes.Any())
|
|
{
|
|
_totalMembers = _leftCount = _rightCount = 0;
|
|
return;
|
|
}
|
|
|
|
_totalMembers = _treeData.Nodes.Count;
|
|
_leftCount = _treeData.Nodes.Count(n => n.NetworkLeg == 0);
|
|
_rightCount = _treeData.Nodes.Count(n => n.NetworkLeg == 1);
|
|
}
|
|
|
|
private async Task ExpandAll()
|
|
{
|
|
try { await JS.InvokeVoidAsync("AdminOrgChart.expandAll"); } catch { }
|
|
}
|
|
|
|
private async Task CollapseAll()
|
|
{
|
|
try { await JS.InvokeVoidAsync("AdminOrgChart.collapseAll"); } catch { }
|
|
}
|
|
|
|
private async Task FitChart()
|
|
{
|
|
try { await JS.InvokeVoidAsync("AdminOrgChart.fit"); } catch { }
|
|
}
|
|
|
|
private async Task ExportPng()
|
|
{
|
|
try { await JS.InvokeVoidAsync("AdminOrgChart.exportPng"); } catch { }
|
|
}
|
|
|
|
private async Task GoBack()
|
|
{
|
|
if (_navigationHistory.Count > 0)
|
|
{
|
|
var previousUserId = _navigationHistory.Pop();
|
|
_currentViewUserId = previousUserId;
|
|
await LoadSubTree(previousUserId);
|
|
}
|
|
}
|
|
|
|
private async Task GoToRoot()
|
|
{
|
|
if (_searchUserId.HasValue)
|
|
{
|
|
_navigationHistory.Clear();
|
|
await LoadSubTree(_searchUserId.Value);
|
|
}
|
|
}
|
|
|
|
private async Task ViewUserTree(long userId)
|
|
{
|
|
await LoadSubTree(userId);
|
|
}
|
|
|
|
private void ViewUserDetails(long userId)
|
|
{
|
|
NavigationManager.NavigateTo($"/network/user-info/{userId}");
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
try
|
|
{
|
|
await JS.InvokeVoidAsync("AdminOrgChart.dispose");
|
|
}
|
|
catch { }
|
|
|
|
_dotNetRef?.Dispose();
|
|
}
|
|
}
|