diff --git a/.gitignore b/.gitignore index 1c55f34..0f13c79 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files *.rsuser diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 88d2fa8..0a43ad1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,4 +1,4 @@ # Docs moved to totalDoc See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation. - \ No newline at end of file + \ No newline at end of file diff --git a/docs/CUSTOMER-SERVICES.md b/docs/CUSTOMER-SERVICES.md index 05ff8bd..41a173e 100644 --- a/docs/CUSTOMER-SERVICES.md +++ b/docs/CUSTOMER-SERVICES.md @@ -101,7 +101,6 @@ | 6.6 | `UserOrderContract.SubmitShopBuyOrder` | `UserOrderContract` | `/checkout-summary` | ثبت سفارش و پرداخت از کیف پول | | 6.7 | `OrderService.GetOrdersAsync` | `UserOrderContract.GetCustomerOrders` | `/orders` | لیست سفارش‌های کاربر | | 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.11 | `WalletService.GetBalancesAsync` | `UserWalletContract` | `/checkout-summary` | بررسی موجودی برای پرداخت سفارش | diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index f7089c0..8d8f9e7 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -10,9 +10,8 @@ - - - + + diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor index beba9e2..c455966 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor +++ b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor @@ -22,9 +22,11 @@ else if (_addresses.Count == 0) { - هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید. + هیچ آدرسی ثبت نشده است. می‌توانید همین‌جا آدرس جدید اضافه کنید. - افزودن آدرس + افزودن آدرس } else { @@ -41,13 +43,25 @@ @address.Title @address.Address - @if (address.IsDefault) - { - پیش‌فرض - } + + @if (address.IsDefault) + { + پیش‌فرض + } + + + + } + افزودن آدرس جدید } diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs index 1185e0b..3068fb4 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Components; using CMSMicroservice.Protobuf.Protos.UserAddress; +using FrontOffice.Main.Pages.Profile.Components; using FrontOffice.Main.Utilities; using MudBlazor; @@ -61,12 +62,14 @@ public partial class Checkout else { _addresses = new(); + _selectedAddress = null; } } catch (Exception ex) { Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); _addresses = new(); + _selectedAddress = null; } finally { @@ -75,6 +78,30 @@ public partial class Checkout } } + private async Task OpenAddAddressDialog() + { + var dialog = await DialogService.ShowAsync("افزودن آدرس جدید"); + 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("ویرایش آدرس", new DialogParameters + { + { x => x.Model, address } + }); + var result = await dialog.Result; + if (result is { Canceled: false }) + { + await LoadAddresses(); + } + } + private async Task PlaceOrder() { if (!CanPlaceOrder || _selectedAddress is null) diff --git a/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor b/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor index 445b046..5d5cdf3 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor +++ b/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor @@ -52,29 +52,47 @@
} - @* Stats Bar *@ + @* Stats Bar — فقط آمار خود کاربر (نه نودهای درخت) *@ @if (_statistics != null && !_isLoading) {
-
- کل اعضا: - @_statistics.TotalMembers +
+
+ کل اعضا: + @_statistics.TotalMembers +
+
+ سازمان چپ: + @_statistics.LeftLegCount +
+
+ سازمان راست: + @_statistics.RightLegCount +
+
+ عمق: + @_statistics.TreeDepth +
+
+ پای ضعیف: + @(_statistics.WeakerLeg == "Left" ? "چپ" : "راست") +
-
- سازمان چپ: - @_statistics.LeftLegCount +
+
+ پکیج طلایی: + چپ @_statistics.GoldLeftLegTotal + · + راست @_statistics.GoldRightLegTotal +
-
- سازمان راست: - @_statistics.RightLegCount -
-
- عمق: - @_statistics.TreeDepth -
-
- پای ضعیف: - @(_statistics.WeakerLeg == "Left" ? "چپ" : "راست") +
+
+ پکیج نقره‌ای: + چپ @_statistics.SilverLeftLegTotal + · + راست @_statistics.SilverRightLegTotal +
} diff --git a/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor.cs b/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor.cs index 4f6ac05..22e34b0 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor.cs @@ -1,3 +1,4 @@ +using DateTimeConverterCL; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; @@ -108,7 +109,9 @@ public partial class OrganizationChart : IAsyncDisposable isActive = n.IsActive, isClubActive = n.IsClubActive, activationWeekNumber = n.ActivationWeekNumber, - joinedAt = n.JoinedAt, + registeredAt = n.JoinedAt.HasValue + ? n.JoinedAt.Value.ToLocalTime().MiladiToJalali() + : "", referralCode = n.ReferralCode, packageName = n.PackageName ?? string.Empty }).ToArray(); diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor index b894fb3..9fcf078 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor @@ -21,9 +21,11 @@ else if (_addresses.Count == 0) { - هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید. + هیچ آدرسی ثبت نشده است. می‌توانید همین‌جا آدرس جدید اضافه کنید. - افزودن آدرس + افزودن آدرس } else { @@ -40,13 +42,25 @@ @address.Title @address.Address - @if (address.IsDefault) - { - پیش‌فرض - } + + @if (address.IsDefault) + { + پیش‌فرض + } + + + + } + افزودن آدرس جدید } diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs index 0c8aedf..479c633 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs @@ -1,5 +1,6 @@ using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.UserOrder; +using FrontOffice.Main.Pages.Profile.Components; using FrontOffice.Main.Shared; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; @@ -72,12 +73,14 @@ public partial class CheckoutSummary : ComponentBase else { _addresses = new(); + _selectedAddress = null; } } catch (Exception ex) { Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); _addresses = new(); + _selectedAddress = null; } finally { @@ -86,6 +89,30 @@ public partial class CheckoutSummary : ComponentBase } } + private async Task OpenAddAddressDialog() + { + var dialog = await DialogService.ShowAsync("افزودن آدرس جدید"); + 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("ویرایش آدرس", new DialogParameters + { + { x => x.Model, address } + }); + var result = await dialog.Result; + if (result is { Canceled: false }) + { + await LoadAddresses(); + } + } + private async Task PlaceOrder() { if (!CanPlaceOrder || _selectedAddress is null) diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor index abfd053..d94751d 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor @@ -112,30 +112,6 @@ else } - - @* نمایش PV سفارش *@ - @if (_pvData != null && _pvData.TotalPv > 0) - { - - - - - امتیاز فروش (PV) - - @foreach (var product in _pvData.Products) - { - - @product.ProductTitle × @product.Quantity - @string.Format("{0:N0}", product.TotalPv) PV - - } - - - جمع PV: - @string.Format("{0:N0}", _pvData.TotalPv) PV - - - } diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs index a8ed6fa..ec8abbf 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs @@ -12,17 +12,12 @@ public partial class OrderDetail : ComponentBase [Parameter] public long id { get; set; } private GetUserOrderResponse? _order; - private CalculateOrderPVResponse? _pvData; private bool _loading; protected override async Task OnParametersSetAsync() { _loading = true; _order = await OrderService.GetOrderAsync(id); - if (_order != null) - { - _pvData = await OrderService.CalculateOrderPVAsync(id); - } _loading = false; } @@ -52,4 +47,3 @@ public partial class OrderDetail : ComponentBase private static string GetProductImageUrl(string? imageUrl) => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/'); } - diff --git a/src/FrontOffice.Main/Utilities/NetworkMembershipDtos.cs b/src/FrontOffice.Main/Utilities/NetworkMembershipDtos.cs index 8bbdb37..54646a9 100644 --- a/src/FrontOffice.Main/Utilities/NetworkMembershipDtos.cs +++ b/src/FrontOffice.Main/Utilities/NetworkMembershipDtos.cs @@ -14,7 +14,7 @@ public class NetworkNodeDto public NetworkNodeDto? RightChild { get; set; } public int Level { get; set; } public bool IsActive { get; set; } = true; - public DateTime? JoinedAt { get; set; } + public DateTime? JoinedAt { get; set; } // تاریخ ثبت‌نام (UserCreated) public bool IsClubActive { get; set; } public string? ActivationWeekNumber { get; set; } /// @@ -26,6 +26,18 @@ public class NetworkNodeDto /// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای) /// 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; } } /// @@ -53,6 +65,11 @@ public class FlatNetworkNodeDto /// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای) /// 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; } } /// @@ -92,7 +109,11 @@ public class NetworkTreeDto ActivationWeekNumber = node.ActivationWeekNumber, JoinedAt = node.JoinedAt, ReferralCode = node.ReferralCode, - PackageName = node.PackageName + PackageName = node.PackageName, + GoldLeftLegTotal = node.GoldLeftLegTotal, + GoldRightLegTotal = node.GoldRightLegTotal, + SilverLeftLegTotal = node.SilverLeftLegTotal, + SilverRightLegTotal = node.SilverRightLegTotal }; result.Add(flatNode); @@ -125,10 +146,19 @@ public class LastMemberDto /// public class NetworkStatisticsDto { + /// اعضای فعال باشگاه در پای چپ public int LeftLegCount { get; set; } + /// اعضای فعال باشگاه در پای راست public int RightLegCount { get; set; } + /// LeftLegCount + RightLegCount public int TotalMembers { get; set; } public int TreeDepth { get; set; } public string WeakerLeg { get; set; } = string.Empty; // "Left" or "Right" public LastMemberDto? LastMember { get; set; } + + /// تعداد اعضای فعال با پکیج طلایی/نقره‌ای جاری در هر پا — نوار پایین شجره + public int GoldLeftLegTotal { get; set; } + public int GoldRightLegTotal { get; set; } + public int SilverLeftLegTotal { get; set; } + public int SilverRightLegTotal { get; set; } } diff --git a/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs b/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs index 4983855..92e11df 100644 --- a/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs +++ b/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs @@ -96,6 +96,10 @@ public class NetworkMembershipService TotalMembers = response.TotalMembers, TreeDepth = response.MaxDepth, WeakerLeg = response.WeakerLeg, + GoldLeftLegTotal = response.GoldLeftLegTotal, + GoldRightLegTotal = response.GoldRightLegTotal, + SilverLeftLegTotal = response.SilverLeftLegTotal, + SilverRightLegTotal = response.SilverRightLegTotal, LastMember = response.LastMember != null ? new LastMemberDto { UserId = response.LastMember.UserId, @@ -138,8 +142,13 @@ public class NetworkMembershipService IsClubActive = node.IsClubActive, ReferralCode = node.ReferralCode, PackageName = string.IsNullOrWhiteSpace(node.PackageName) ? null : node.PackageName, + GoldLeftLegTotal = node.GoldLeftLegTotal, + GoldRightLegTotal = node.GoldRightLegTotal, + SilverLeftLegTotal = node.SilverLeftLegTotal, + SilverRightLegTotal = node.SilverRightLegTotal, ActivationWeekNumber = node.ActivationWeekDefinitionId?.ToString(), - JoinedAt = node.JoinedAt?.ToDateTime(), + JoinedAt = node.UserCreated?.ToDateTime() + ?? node.JoinedAt?.ToDateTime(), LeftChild = MapNodeFromProto(node.LeftChild), RightChild = MapNodeFromProto(node.RightChild) }; diff --git a/src/FrontOffice.Main/Utilities/OrderService.cs b/src/FrontOffice.Main/Utilities/OrderService.cs index 39d9cfb..5f3ca8f 100644 --- a/src/FrontOffice.Main/Utilities/OrderService.cs +++ b/src/FrontOffice.Main/Utilities/OrderService.cs @@ -97,22 +97,4 @@ public class OrderService return 0.10; } } - - /// - /// محاسبه PV سفارش - /// - public async Task CalculateOrderPVAsync(long orderId) - { - try - { - return await _userOrderContractClient.CalculateOrderPVAsync(new CalculateOrderPVRequest - { - OrderId = orderId - }); - } - catch - { - return null; - } - } } \ No newline at end of file diff --git a/src/FrontOffice.Main/wwwroot/css/org-chart.css b/src/FrontOffice.Main/wwwroot/css/org-chart.css index 4ac39ab..b5124d8 100644 --- a/src/FrontOffice.Main/wwwroot/css/org-chart.css +++ b/src/FrontOffice.Main/wwwroot/css/org-chart.css @@ -118,6 +118,16 @@ 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 { font-size: 9px; color: #6c757d; @@ -141,6 +151,7 @@ max-width: 120px; } +/* D3: تعداد پکیج طلایی/نقره‌ای فقط در نوار پایین شجره (.org-chart-stats) */ /* Referral Code - only for club active members */ .node-referral { display: flex; @@ -370,14 +381,27 @@ /* Stats Bar */ .org-chart-stats { display: flex; + flex-direction: column; align-items: center; justify-content: center; - gap: 24px; + gap: 8px; padding: 12px 16px; background: #f8f9fa; 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 { display: flex; align-items: center; @@ -385,6 +409,21 @@ 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 { color: #6c757d; } @@ -448,10 +487,13 @@ } .org-chart-stats { - flex-wrap: wrap; - gap: 12px; + gap: 8px; padding: 10px; } + + .org-chart-stats-row { + gap: 12px; + } .stat-item { font-size: 12px; diff --git a/src/FrontOffice.Main/wwwroot/js/org-chart.js b/src/FrontOffice.Main/wwwroot/js/org-chart.js index 0de2b93..19c56f5 100644 --- a/src/FrontOffice.Main/wwwroot/js/org-chart.js +++ b/src/FrontOffice.Main/wwwroot/js/org-chart.js @@ -38,7 +38,7 @@ window.OrgChart = { .container('#' + containerId) .data(data) .nodeWidth((d) => 140) - .nodeHeight((d) => 85) + .nodeHeight((d) => 98) .childrenMargin((d) => 50) .compactMarginBetween((d) => 15) .compactMarginPair((d) => 15) @@ -86,6 +86,7 @@ window.OrgChart = {
${firstChar}
${data.fullName || 'بدون نام'}
+ ${data.registeredAt ? `
${data.registeredAt}
` : ''}
L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}
${packageBadge} ${referralCodeHtml} @@ -296,7 +297,7 @@ window.OrgChart = { isActive: node.isActive !== false, isClubActive: node.isClubActive || false, activationWeekNumber: node.activationWeekNumber, - joinedAt: node.joinedAt + registeredAt: node.registeredAt || node.joinedAt || '' }; result.push(flatNode);