Compare commits

...

7 Commits

Author SHA1 Message Date
masoodafar-web 690aaa714b Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 4m15s
2026-09-04 07:26:01 +03:30
masoodafar-web 8252e408b6 feat(organization-chart): update registration date handling and styling
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 3m41s
- 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
masoodafar-web 186abe042f feat(network-statistics): add total members and leg count properties with localization
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 4m23s
- Introduced new properties for total members, left leg count, and right leg count in the NetworkStatisticsDto class to enhance data representation.
- Added localization comments in Persian for better understanding of the new properties.
- Updated the .gitignore file to ensure consistency with the latest Visual Studio ignore patterns.

These changes improve the clarity and usability of the network statistics, providing a more comprehensive view of member distribution.
2026-09-04 04:14:36 +03:30
masoodafar-web c6089f8dc3 refactor(organization-chart): update terminology and documentation for clarity
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 7m35s
- Changed terminology in the organization chart from "امتیاز" to "پکیج" for gold and silver packages to enhance clarity.
- Updated comments and documentation in the codebase to reflect the new terminology and improve understanding of the statistics being displayed.
- Adjusted the CHANGELOG to indicate the move of documentation to totalDoc.

These changes improve the overall clarity and usability of the organization chart and related documentation.
2026-08-28 18:56:34 +03:30
masoodafar-web 101687f9db feat(organization-chart): update statistics display and improve layout
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 4m44s
- Updated the organization chart to include additional statistics such as left and right leg counts, tree depth, and weaker leg identification for enhanced user insights.
- Improved the layout and styling of the statistics bar for better visibility and user experience.
- Adjusted the CSS for the organization chart to refine the overall presentation and responsiveness.

These changes enhance the clarity and usability of the organization chart, providing users with essential metrics at a glance.
2026-08-27 21:30:06 +03:30
masoodafar-web 307302e33d feat(organization-chart): enhance organization chart with leg totals and styling improvements
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 6m9s
- Updated the organization chart to include gold and silver leg totals for better visibility of user metrics.
- Modified the CSS for the chart to improve layout and styling, ensuring a more user-friendly interface.
- Adjusted JavaScript to dynamically display leg scores, enhancing the overall interactivity of the chart.

These changes improve the clarity and usability of the organization chart, providing users with essential metrics at a glance.
2026-08-25 22:39:30 +03:30
masoodafar-web c243b59113 feat(address): enhance address management in checkout and summary components
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 6m48s
- Updated the Checkout and CheckoutSummary components to allow users to add new addresses directly from the interface, improving accessibility and user experience.
- Introduced dialog functionality for adding and editing addresses, streamlining the address management process.
- Removed the calculation of order PV from the OrderDetail component, simplifying the order details view and improving performance.

These changes enhance the usability of the checkout process by making address management more intuitive and efficient.
2026-08-25 21:40:38 +03:30
17 changed files with 228 additions and 93 deletions
-1
View File
@@ -101,7 +101,6 @@
| 6.6 | `UserOrderContract.SubmitShopBuyOrder` | `UserOrderContract` | `/checkout-summary` | ثبت سفارش و پرداخت از کیف پول | | 6.6 | `UserOrderContract.SubmitShopBuyOrder` | `UserOrderContract` | `/checkout-summary` | ثبت سفارش و پرداخت از کیف پول |
| 6.7 | `OrderService.GetOrdersAsync` | `UserOrderContract.GetCustomerOrders` | `/orders` | لیست سفارش‌های کاربر | | 6.7 | `OrderService.GetOrdersAsync` | `UserOrderContract.GetCustomerOrders` | `/orders` | لیست سفارش‌های کاربر |
| 6.8 | `OrderService.GetOrderAsync` | `UserOrderContract.GetCustomerOrder` | `/order/{id}`, `/order-tracking/{id}` | جزئیات یک سفارش | | 6.8 | `OrderService.GetOrderAsync` | `UserOrderContract.GetCustomerOrder` | `/order/{id}`, `/order-tracking/{id}` | جزئیات یک سفارش |
| 6.9 | `OrderService.CalculateOrderPVAsync` | `UserOrderContract.CalculateOrderPV` | `/order/{id}` | محاسبه PV سفارش |
| 6.10 | `VATService.GetRateAsync` | `UserOrderContract` (via scope) | Store/Discount صفحات قیمت | نرخ مالیات بر ارزش افزوده (کش روزانه) | | 6.10 | `VATService.GetRateAsync` | `UserOrderContract` (via scope) | Store/Discount صفحات قیمت | نرخ مالیات بر ارزش افزوده (کش روزانه) |
| 6.11 | `WalletService.GetBalancesAsync` | `UserWalletContract` | `/checkout-summary` | بررسی موجودی برای پرداخت سفارش | | 6.11 | `WalletService.GetBalancesAsync` | `UserWalletContract` | `/checkout-summary` | بررسی موجودی برای پرداخت سفارش |
+2 -3
View File
@@ -10,9 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" /> <PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf --> <!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf (CI/CD packs → Nexus) -->
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.204" /> <PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.211" />
<!-- <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
<PackageReference Include="MudBlazor" Version="8.14.0" /> <PackageReference Include="MudBlazor" Version="8.14.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" /> <PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Mapster" Version="7.4.0" /> <PackageReference Include="Mapster" Version="7.4.0" />
@@ -22,9 +22,11 @@
else if (_addresses.Count == 0) else if (_addresses.Count == 0)
{ {
<MudAlert Severity="Severity.Warning"> <MudAlert Severity="Severity.Warning">
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید. هیچ آدرسی ثبت نشده است. می‌توانید همین‌جا آدرس جدید اضافه کنید.
</MudAlert> </MudAlert>
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton> <MudButton Class="mt-2" Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس</MudButton>
} }
else else
{ {
@@ -41,13 +43,25 @@
<MudText Typo="Typo.subtitle2">@address.Title</MudText> <MudText Typo="Typo.subtitle2">@address.Title</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText> <MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
</MudStack> </MudStack>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
@if (address.IsDefault) @if (address.IsDefault)
{ {
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیش‌فرض</MudChip> <MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">پیش‌فرض</MudChip>
} }
<span @onclick:stopPropagation="true">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Color="Color.Primary"
aria-label="ویرایش آدرس"
OnClick="@(() => OpenEditAddressDialog(address))" />
</span>
</MudStack>
</MudStack> </MudStack>
</MudPaper> </MudPaper>
} }
<MudButton Class="mt-2" Variant="Variant.Text" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس جدید</MudButton>
</MudStack> </MudStack>
} }
</MudPaper> </MudPaper>
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.UserAddress;
using FrontOffice.Main.Pages.Profile.Components;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using MudBlazor; using MudBlazor;
@@ -61,12 +62,14 @@ public partial class Checkout
else else
{ {
_addresses = new(); _addresses = new();
_selectedAddress = null;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error);
_addresses = new(); _addresses = new();
_selectedAddress = null;
} }
finally finally
{ {
@@ -75,6 +78,30 @@ public partial class Checkout
} }
} }
private async Task OpenAddAddressDialog()
{
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (result is { Canceled: false })
{
await AuthService.RefreshTokenAsync();
await LoadAddresses();
}
}
private async Task OpenEditAddressDialog(CustomerAddressModel address)
{
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
{
{ x => x.Model, address }
});
var result = await dialog.Result;
if (result is { Canceled: false })
{
await LoadAddresses();
}
}
private async Task PlaceOrder() private async Task PlaceOrder()
{ {
if (!CanPlaceOrder || _selectedAddress is null) if (!CanPlaceOrder || _selectedAddress is null)
@@ -52,10 +52,11 @@
<div id="org-chart-container" class="org-chart-container"></div> <div id="org-chart-container" class="org-chart-container"></div>
} }
@* Stats Bar *@ @* Stats Bar — فقط آمار خود کاربر (نه نودهای درخت) *@
@if (_statistics != null && !_isLoading) @if (_statistics != null && !_isLoading)
{ {
<div class="org-chart-stats"> <div class="org-chart-stats">
<div class="org-chart-stats-row">
<div class="stat-item"> <div class="stat-item">
<span class="stat-label">کل اعضا:</span> <span class="stat-label">کل اعضا:</span>
<span class="stat-value">@_statistics.TotalMembers</span> <span class="stat-value">@_statistics.TotalMembers</span>
@@ -77,6 +78,23 @@
<span class="stat-value">@(_statistics.WeakerLeg == "Left" ? "چپ" : "راست")</span> <span class="stat-value">@(_statistics.WeakerLeg == "Left" ? "چپ" : "راست")</span>
</div> </div>
</div> </div>
<div class="org-chart-stats-row leg-scores">
<div class="stat-item leg-score-gold">
<span class="stat-label">پکیج طلایی:</span>
<span class="stat-value left">چپ @_statistics.GoldLeftLegTotal</span>
<span class="stat-sep">·</span>
<span class="stat-value right">راست @_statistics.GoldRightLegTotal</span>
</div>
</div>
<div class="org-chart-stats-row leg-scores">
<div class="stat-item leg-score-silver">
<span class="stat-label">پکیج نقره‌ای:</span>
<span class="stat-value left">چپ @_statistics.SilverLeftLegTotal</span>
<span class="stat-sep">·</span>
<span class="stat-value right">راست @_statistics.SilverRightLegTotal</span>
</div>
</div>
</div>
} }
</div> </div>
@@ -1,3 +1,4 @@
using DateTimeConverterCL;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
@@ -108,7 +109,9 @@ public partial class OrganizationChart : IAsyncDisposable
isActive = n.IsActive, isActive = n.IsActive,
isClubActive = n.IsClubActive, isClubActive = n.IsClubActive,
activationWeekNumber = n.ActivationWeekNumber, activationWeekNumber = n.ActivationWeekNumber,
joinedAt = n.JoinedAt, registeredAt = n.JoinedAt.HasValue
? n.JoinedAt.Value.ToLocalTime().MiladiToJalali()
: "",
referralCode = n.ReferralCode, referralCode = n.ReferralCode,
packageName = n.PackageName ?? string.Empty packageName = n.PackageName ?? string.Empty
}).ToArray(); }).ToArray();
@@ -21,9 +21,11 @@
else if (_addresses.Count == 0) else if (_addresses.Count == 0)
{ {
<MudAlert Severity="Severity.Warning"> <MudAlert Severity="Severity.Warning">
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید. هیچ آدرسی ثبت نشده است. می‌توانید همین‌جا آدرس جدید اضافه کنید.
</MudAlert> </MudAlert>
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton> <MudButton Class="mt-2" Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس</MudButton>
} }
else else
{ {
@@ -40,13 +42,25 @@
<MudText Typo="Typo.subtitle2">@address.Title</MudText> <MudText Typo="Typo.subtitle2">@address.Title</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText> <MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
</MudStack> </MudStack>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
@if (address.IsDefault) @if (address.IsDefault)
{ {
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیش‌فرض</MudChip> <MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">پیش‌فرض</MudChip>
} }
<span @onclick:stopPropagation="true">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Color="Color.Primary"
aria-label="ویرایش آدرس"
OnClick="@(() => OpenEditAddressDialog(address))" />
</span>
</MudStack>
</MudStack> </MudStack>
</MudPaper> </MudPaper>
} }
<MudButton Class="mt-2" Variant="Variant.Text" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس جدید</MudButton>
</MudStack> </MudStack>
} }
</MudPaper> </MudPaper>
@@ -1,5 +1,6 @@
using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.UserAddress;
using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserOrder;
using FrontOffice.Main.Pages.Profile.Components;
using FrontOffice.Main.Shared; using FrontOffice.Main.Shared;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@@ -72,12 +73,14 @@ public partial class CheckoutSummary : ComponentBase
else else
{ {
_addresses = new(); _addresses = new();
_selectedAddress = null;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error);
_addresses = new(); _addresses = new();
_selectedAddress = null;
} }
finally finally
{ {
@@ -86,6 +89,30 @@ public partial class CheckoutSummary : ComponentBase
} }
} }
private async Task OpenAddAddressDialog()
{
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (result is { Canceled: false })
{
await AuthService.RefreshTokenAsync();
await LoadAddresses();
}
}
private async Task OpenEditAddressDialog(CustomerAddressModel address)
{
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
{
{ x => x.Model, address }
});
var result = await dialog.Result;
if (result is { Canceled: false })
{
await LoadAddresses();
}
}
private async Task PlaceOrder() private async Task PlaceOrder()
{ {
if (!CanPlaceOrder || _selectedAddress is null) if (!CanPlaceOrder || _selectedAddress is null)
@@ -112,30 +112,6 @@ else
</MudStack> </MudStack>
} }
</MudStack> </MudStack>
@* نمایش PV سفارش *@
@if (_pvData != null && _pvData.TotalPv > 0)
{
<MudDivider Class="my-2" />
<MudStack Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Info" Size="Size.Small" />
<MudText Typo="Typo.subtitle2" Color="Color.Info">امتیاز فروش (PV)</MudText>
</MudStack>
@foreach (var product in _pvData.Products)
{
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.caption" Class="mud-text-secondary">@product.ProductTitle × @product.Quantity</MudText>
<MudText Typo="Typo.caption">@string.Format("{0:N0}", product.TotalPv) PV</MudText>
</MudStack>
}
<MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="fw-bold">جمع PV:</MudText>
<MudText Typo="Typo.body2" Color="Color.Info" Class="fw-bold">@string.Format("{0:N0}", _pvData.TotalPv) PV</MudText>
</MudStack>
</MudStack>
}
</MudStack> </MudStack>
</MudPaper> </MudPaper>
</MudContainer> </MudContainer>
@@ -12,17 +12,12 @@ public partial class OrderDetail : ComponentBase
[Parameter] public long id { get; set; } [Parameter] public long id { get; set; }
private GetUserOrderResponse? _order; private GetUserOrderResponse? _order;
private CalculateOrderPVResponse? _pvData;
private bool _loading; private bool _loading;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_loading = true; _loading = true;
_order = await OrderService.GetOrderAsync(id); _order = await OrderService.GetOrderAsync(id);
if (_order != null)
{
_pvData = await OrderService.CalculateOrderPVAsync(id);
}
_loading = false; _loading = false;
} }
@@ -52,4 +47,3 @@ public partial class OrderDetail : ComponentBase
private static string GetProductImageUrl(string? imageUrl) private static string GetProductImageUrl(string? imageUrl)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/'); => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
} }
@@ -14,7 +14,7 @@ public class NetworkNodeDto
public NetworkNodeDto? RightChild { get; set; } public NetworkNodeDto? RightChild { get; set; }
public int Level { get; set; } public int Level { get; set; }
public bool IsActive { get; set; } = true; public bool IsActive { get; set; } = true;
public DateTime? JoinedAt { get; set; } public DateTime? JoinedAt { get; set; } // تاریخ ثبت‌نام (UserCreated)
public bool IsClubActive { get; set; } public bool IsClubActive { get; set; }
public string? ActivationWeekNumber { get; set; } public string? ActivationWeekNumber { get; set; }
/// <summary> /// <summary>
@@ -26,6 +26,18 @@ public class NetworkNodeDto
/// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای) /// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای)
/// </summary> /// </summary>
public string? PackageName { get; set; } public string? PackageName { get; set; }
/// <summary>تعداد اعضای فعال با پکیج طلایی جاری در پای چپ</summary>
public int GoldLeftLegTotal { get; set; }
/// <summary>تعداد اعضای فعال با پکیج طلایی جاری در پای راست</summary>
public int GoldRightLegTotal { get; set; }
/// <summary>تعداد اعضای فعال با پکیج نقره‌ای جاری در پای چپ</summary>
public int SilverLeftLegTotal { get; set; }
/// <summary>تعداد اعضای فعال با پکیج نقره‌ای جاری در پای راست</summary>
public int SilverRightLegTotal { get; set; }
} }
/// <summary> /// <summary>
@@ -53,6 +65,11 @@ public class FlatNetworkNodeDto
/// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای) /// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای)
/// </summary> /// </summary>
public string? PackageName { get; set; } public string? PackageName { get; set; }
public int GoldLeftLegTotal { get; set; }
public int GoldRightLegTotal { get; set; }
public int SilverLeftLegTotal { get; set; }
public int SilverRightLegTotal { get; set; }
} }
/// <summary> /// <summary>
@@ -92,7 +109,11 @@ public class NetworkTreeDto
ActivationWeekNumber = node.ActivationWeekNumber, ActivationWeekNumber = node.ActivationWeekNumber,
JoinedAt = node.JoinedAt, JoinedAt = node.JoinedAt,
ReferralCode = node.ReferralCode, ReferralCode = node.ReferralCode,
PackageName = node.PackageName PackageName = node.PackageName,
GoldLeftLegTotal = node.GoldLeftLegTotal,
GoldRightLegTotal = node.GoldRightLegTotal,
SilverLeftLegTotal = node.SilverLeftLegTotal,
SilverRightLegTotal = node.SilverRightLegTotal
}; };
result.Add(flatNode); result.Add(flatNode);
@@ -125,10 +146,19 @@ public class LastMemberDto
/// </summary> /// </summary>
public class NetworkStatisticsDto public class NetworkStatisticsDto
{ {
/// <summary>اعضای فعال باشگاه در پای چپ</summary>
public int LeftLegCount { get; set; } public int LeftLegCount { get; set; }
/// <summary>اعضای فعال باشگاه در پای راست</summary>
public int RightLegCount { get; set; } public int RightLegCount { get; set; }
/// <summary>LeftLegCount + RightLegCount</summary>
public int TotalMembers { get; set; } public int TotalMembers { get; set; }
public int TreeDepth { get; set; } public int TreeDepth { get; set; }
public string WeakerLeg { get; set; } = string.Empty; // "Left" or "Right" public string WeakerLeg { get; set; } = string.Empty; // "Left" or "Right"
public LastMemberDto? LastMember { get; set; } public LastMemberDto? LastMember { get; set; }
/// <summary>تعداد اعضای فعال با پکیج طلایی/نقره‌ای جاری در هر پا — نوار پایین شجره</summary>
public int GoldLeftLegTotal { get; set; }
public int GoldRightLegTotal { get; set; }
public int SilverLeftLegTotal { get; set; }
public int SilverRightLegTotal { get; set; }
} }
@@ -96,6 +96,10 @@ public class NetworkMembershipService
TotalMembers = response.TotalMembers, TotalMembers = response.TotalMembers,
TreeDepth = response.MaxDepth, TreeDepth = response.MaxDepth,
WeakerLeg = response.WeakerLeg, WeakerLeg = response.WeakerLeg,
GoldLeftLegTotal = response.GoldLeftLegTotal,
GoldRightLegTotal = response.GoldRightLegTotal,
SilverLeftLegTotal = response.SilverLeftLegTotal,
SilverRightLegTotal = response.SilverRightLegTotal,
LastMember = response.LastMember != null ? new LastMemberDto LastMember = response.LastMember != null ? new LastMemberDto
{ {
UserId = response.LastMember.UserId, UserId = response.LastMember.UserId,
@@ -138,8 +142,13 @@ public class NetworkMembershipService
IsClubActive = node.IsClubActive, IsClubActive = node.IsClubActive,
ReferralCode = node.ReferralCode, ReferralCode = node.ReferralCode,
PackageName = string.IsNullOrWhiteSpace(node.PackageName) ? null : node.PackageName, PackageName = string.IsNullOrWhiteSpace(node.PackageName) ? null : node.PackageName,
GoldLeftLegTotal = node.GoldLeftLegTotal,
GoldRightLegTotal = node.GoldRightLegTotal,
SilverLeftLegTotal = node.SilverLeftLegTotal,
SilverRightLegTotal = node.SilverRightLegTotal,
ActivationWeekNumber = node.ActivationWeekDefinitionId?.ToString(), ActivationWeekNumber = node.ActivationWeekDefinitionId?.ToString(),
JoinedAt = node.JoinedAt?.ToDateTime(), JoinedAt = node.UserCreated?.ToDateTime()
?? node.JoinedAt?.ToDateTime(),
LeftChild = MapNodeFromProto(node.LeftChild), LeftChild = MapNodeFromProto(node.LeftChild),
RightChild = MapNodeFromProto(node.RightChild) RightChild = MapNodeFromProto(node.RightChild)
}; };
@@ -97,22 +97,4 @@ public class OrderService
return 0.10; return 0.10;
} }
} }
/// <summary>
/// محاسبه PV سفارش
/// </summary>
public async Task<CalculateOrderPVResponse?> CalculateOrderPVAsync(long orderId)
{
try
{
return await _userOrderContractClient.CalculateOrderPVAsync(new CalculateOrderPVRequest
{
OrderId = orderId
});
}
catch
{
return null;
}
}
} }
+45 -3
View File
@@ -118,6 +118,16 @@
line-height: 1.2; line-height: 1.2;
} }
.node-registered-at {
font-size: 8px;
color: #6c757d;
margin-top: 1px;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.node-level-sm { .node-level-sm {
font-size: 9px; font-size: 9px;
color: #6c757d; color: #6c757d;
@@ -141,6 +151,7 @@
max-width: 120px; max-width: 120px;
} }
/* D3: تعداد پکیج طلایی/نقره‌ای فقط در نوار پایین شجره (.org-chart-stats) */
/* Referral Code - only for club active members */ /* Referral Code - only for club active members */
.node-referral { .node-referral {
display: flex; display: flex;
@@ -370,14 +381,27 @@
/* Stats Bar */ /* Stats Bar */
.org-chart-stats { .org-chart-stats {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 24px; gap: 8px;
padding: 12px 16px; padding: 12px 16px;
background: #f8f9fa; background: #f8f9fa;
border-top: 1px solid #e9ecef; border-top: 1px solid #e9ecef;
} }
.org-chart-stats-row {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 24px;
}
.org-chart-stats-row.leg-scores {
gap: 12px;
}
.stat-item { .stat-item {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -385,6 +409,21 @@
font-size: 13px; font-size: 13px;
} }
.stat-item.leg-score-gold .stat-label {
color: #b8860b;
font-weight: 600;
}
.stat-item.leg-score-silver .stat-label {
color: #607d8b;
font-weight: 600;
}
.stat-sep {
color: #adb5bd;
margin: 0 2px;
}
.stat-label { .stat-label {
color: #6c757d; color: #6c757d;
} }
@@ -448,11 +487,14 @@
} }
.org-chart-stats { .org-chart-stats {
flex-wrap: wrap; gap: 8px;
gap: 12px;
padding: 10px; padding: 10px;
} }
.org-chart-stats-row {
gap: 12px;
}
.stat-item { .stat-item {
font-size: 12px; font-size: 12px;
} }
+3 -2
View File
@@ -38,7 +38,7 @@ window.OrgChart = {
.container('#' + containerId) .container('#' + containerId)
.data(data) .data(data)
.nodeWidth((d) => 140) .nodeWidth((d) => 140)
.nodeHeight((d) => 85) .nodeHeight((d) => 98)
.childrenMargin((d) => 50) .childrenMargin((d) => 50)
.compactMarginBetween((d) => 15) .compactMarginBetween((d) => 15)
.compactMarginPair((d) => 15) .compactMarginPair((d) => 15)
@@ -86,6 +86,7 @@ window.OrgChart = {
<div class="node-avatar-sm">${firstChar}</div> <div class="node-avatar-sm">${firstChar}</div>
<div class="node-info"> <div class="node-info">
<div class="node-name-sm">${data.fullName || 'بدون نام'}</div> <div class="node-name-sm">${data.fullName || 'بدون نام'}</div>
${data.registeredAt ? `<div class="node-registered-at">${data.registeredAt}</div>` : ''}
<div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div> <div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div>
${packageBadge} ${packageBadge}
${referralCodeHtml} ${referralCodeHtml}
@@ -296,7 +297,7 @@ window.OrgChart = {
isActive: node.isActive !== false, isActive: node.isActive !== false,
isClubActive: node.isClubActive || false, isClubActive: node.isClubActive || false,
activationWeekNumber: node.activationWeekNumber, activationWeekNumber: node.activationWeekNumber,
joinedAt: node.joinedAt registeredAt: node.registeredAt || node.joinedAt || ''
}; };
result.push(flatNode); result.push(flatNode);