feat: Implement Commission and Network Statistics Pages with DTOs and Services

- Added CommissionDashboardPage and CommissionHistoryPage for displaying commission payouts and history.
- Implemented WeeklyBalancePage to show weekly balance details.
- Created NetworkStatisticsPage to display network statistics and tree structure.
- Developed corresponding services (CommissionService, NetworkMembershipService) for data retrieval.
- Introduced DTOs for Commission and Network data structures (CommissionPayoutDto, WeeklyBalanceDto, NetworkStatisticsDto).
- Added mock data generation for testing purposes in services.
- Enhanced UI with MudBlazor components for better user experience.
This commit is contained in:
masoodafar-web
2025-12-04 17:29:16 +03:30
parent a8e6693c70
commit 95c6bf5efa
25 changed files with 1882 additions and 26 deletions
@@ -0,0 +1,149 @@
@attribute [Route(RouteConstants.Commission.Dashboard)]
@using FrontOffice.Main.Utilities
@using MudBlazor
<PageTitle>داشبورد کمیسیون</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<MudStack Spacing="3">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h5">داشبورد کمیسیون</MudText>
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
</MudStack>
@if (_isLoading)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
}
else
{
<!-- فیلترها -->
<MudPaper Elevation="2" Class="pa-3 rounded-lg">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.End">
<MudNumericField @bind-Value="_filterWeekNumber"
Label="شماره هفته"
Variant="Variant.Outlined"
Min="1"
Clearable="true"
Style="max-width: 150px;" />
<MudSelect T="string" @bind-Value="_filterStatus" Label="وضعیت" Variant="Variant.Outlined" Style="max-width: 200px;">
<MudSelectItem T="string" Value="@string.Empty">همه</MudSelectItem>
<MudSelectItem T="string" Value="@("Created")">ایجاد شده</MudSelectItem>
<MudSelectItem T="string" Value="@("Paid")">پرداخت شده</MudSelectItem>
<MudSelectItem T="string" Value="@("WithdrawalRequested")">درخواست برداشت</MudSelectItem>
<MudSelectItem T="string" Value="@("Withdrawn")">برداشت شده</MudSelectItem>
<MudSelectItem T="string" Value="@("Cancelled")">لغو شده</MudSelectItem>
</MudSelect>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ApplyFiltersAsync" StartIcon="@Icons.Material.Filled.FilterList">
اعمال فیلتر
</MudButton>
<MudButton Variant="Variant.Outlined" OnClick="ClearFiltersAsync" StartIcon="@Icons.Material.Filled.Clear">
پاک کردن
</MudButton>
</MudStack>
</MudPaper>
<!-- لیست کمیسیون‌ها -->
@if (_payouts.Any())
{
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
<MudTable Items="_payouts" Hover="true" Striped="true" Dense="true">
<HeaderContent>
<MudTh>هفته</MudTh>
<MudTh>تعداد تعادل</MudTh>
<MudTh>مبلغ کل</MudTh>
<MudTh>وضعیت</MudTh>
<MudTh>تاریخ</MudTh>
<MudTh>عملیات</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>
<MudChip T="string" Color="Color.Default" Size="Size.Small">@context.WeekLabel</MudChip>
</MudTd>
<MudTd>@context.BalancesEarned</MudTd>
<MudTd>
<MudText Color="Color.Success"><strong>@context.AmountFormatted</strong></MudText>
</MudTd>
<MudTd>
<MudChip T="string" Color="@GetStatusColor(context.StatusBadgeColor)" Size="Size.Small" Variant="Variant.Outlined">
@context.Status
</MudChip>
</MudTd>
<MudTd>@context.DatePersian</MudTd>
<MudTd>
<MudButton Size="Size.Small"
Variant="Variant.Text"
Color="Color.Info"
StartIcon="@Icons.Material.Filled.Info"
Href="@($"{RouteConstants.Commission.WeeklyBalance}?week={context.WeekNumber}")">
جزئیات
</MudButton>
</MudTd>
</RowTemplate>
</MudTable>
</MudHidden>
<MudHidden Breakpoint="Breakpoint.MdAndUp">
<MudStack Spacing="2">
@foreach (var payout in _payouts)
{
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
<MudStack Spacing="1">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudChip T="string" Color="Color.Default" Size="Size.Small">@payout.WeekLabel</MudChip>
<MudChip T="string" Color="@GetStatusColor(payout.StatusBadgeColor)" Size="Size.Small" Variant="Variant.Outlined">
@payout.Status
</MudChip>
</MudStack>
<MudText Typo="Typo.h6" Color="Color.Success">@payout.AmountFormatted</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">تعداد تعادل: @payout.BalancesEarned</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">@payout.DatePersian</MudText>
<MudButton Size="Size.Small"
Variant="Variant.Outlined"
Color="Color.Info"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Info"
Href="@($"{RouteConstants.Commission.WeeklyBalance}?week={payout.WeekNumber}")">
مشاهده جزئیات هفته
</MudButton>
</MudStack>
</MudPaper>
}
</MudStack>
</MudHidden>
<!-- Pagination -->
<MudPaper Elevation="0" Class="pa-3">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body2">کل: @_totalCount کمیسیون</MudText>
<MudPagination Count="@GetTotalPages()" Selected="_pageNumber" SelectedChanged="OnPageChangedAsync" Color="Color.Primary" />
</MudStack>
</MudPaper>
}
else
{
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
کمیسیونی برای نمایش وجود ندارد.
</MudAlert>
}
<!-- دکمه‌های بیشتر -->
<MudStack Row="true" Spacing="2">
<MudButton Variant="Variant.Outlined"
Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.History"
Href="@RouteConstants.Commission.History"
FullWidth="true">
تاریخچه کامل
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Info"
StartIcon="@Icons.Material.Filled.BarChart"
Href="@RouteConstants.Commission.WeeklyBalance"
FullWidth="true">
تعادل هفتگی
</MudButton>
</MudStack>
}
</MudStack>
</MudContainer>
@@ -0,0 +1,84 @@
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace FrontOffice.Main.Pages.Commission;
public partial class CommissionDashboardPage : ComponentBase
{
[Inject] private CommissionService CommissionService { get; set; } = default!;
private List<CommissionPayoutDto> _payouts = new();
private int _totalCount;
private int _pageNumber = 1;
private int _pageSize = 10;
private bool _isLoading = true;
private int? _filterWeekNumber;
private string _filterStatus = string.Empty;
protected override async Task OnInitializedAsync()
{
await LoadPayoutsAsync();
}
private async Task LoadPayoutsAsync()
{
try
{
_isLoading = true;
var result = await CommissionService.GetMyCommissionPayoutsAsync(
_filterWeekNumber,
_filterStatus,
_pageNumber,
_pageSize
);
_payouts = result.Payouts;
_totalCount = result.TotalCount;
}
catch (Exception ex)
{
Snackbar.Add($"خطا در دریافت کمیسیون‌ها: {ex.Message}", Severity.Error);
}
finally
{
_isLoading = false;
}
}
private async Task ApplyFiltersAsync()
{
_pageNumber = 1;
await LoadPayoutsAsync();
}
private async Task ClearFiltersAsync()
{
_filterWeekNumber = null;
_filterStatus = string.Empty;
_pageNumber = 1;
await LoadPayoutsAsync();
}
private async Task OnPageChangedAsync(int page)
{
_pageNumber = page;
await LoadPayoutsAsync();
}
private int GetTotalPages()
{
if (_totalCount == 0 || _pageSize == 0)
return 1;
return (int)Math.Ceiling(_totalCount / (double)_pageSize);
}
private Color GetStatusColor(string statusColor) => statusColor.ToLower() switch
{
"success" => Color.Success,
"warning" => Color.Warning,
"error" => Color.Error,
"info" => Color.Info,
_ => Color.Default
};
}
@@ -0,0 +1,91 @@
@attribute [Route(RouteConstants.Commission.History)]
@using FrontOffice.Main.Utilities
@using MudBlazor
<PageTitle>تاریخچه کمیسیون</PageTitle>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="py-6">
<MudStack Spacing="3">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h5">تاریخچه کامل کمیسیون</MudText>
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Commission.Dashboard">بازگشت</MudButton>
</MudStack>
@if (_isLoading)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
}
else if (_payouts.Any())
{
<!-- آمار خلاصه -->
<MudGrid Spacing="2">
<MudItem xs="12" sm="4">
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">کل کمیسیون دریافتی</MudText>
<MudText Typo="Typo.h5" Color="Color.Success">@GetTotalEarnings()</MudText>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="4">
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">تعداد هفته‌ها</MudText>
<MudText Typo="Typo.h5" Color="Color.Primary">@_payouts.Count</MudText>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="4">
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">میانگین هفتگی</MudText>
<MudText Typo="Typo.h5" Color="Color.Info">@GetAverageWeekly()</MudText>
</MudPaper>
</MudItem>
</MudGrid>
<!-- جدول کامل -->
<MudPaper Elevation="2" Class="pa-0 rounded-lg">
<MudTable Items="_payouts" Hover="true" Striped="true" Dense="false" FixedHeader="true" Height="600px">
<HeaderContent>
<MudTh>هفته</MudTh>
<MudTh>تعادل‌ها</MudTh>
<MudTh>مبلغ (تومان)</MudTh>
<MudTh>وضعیت</MudTh>
<MudTh>تاریخ</MudTh>
<MudTh>جزئیات</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="هفته">
<MudChip T="string" Color="Color.Primary" Size="Size.Small" Variant="Variant.Outlined">@context.WeekLabel</MudChip>
</MudTd>
<MudTd DataLabel="تعادل‌ها">
<MudText>@context.BalancesEarned</MudText>
</MudTd>
<MudTd DataLabel="مبلغ">
<MudText Color="Color.Success" Typo="Typo.body1"><strong>@context.AmountFormatted</strong></MudText>
</MudTd>
<MudTd DataLabel="وضعیت">
<MudChip T="string" Color="@GetStatusColor(context.StatusBadgeColor)" Size="Size.Small">
@context.Status
</MudChip>
</MudTd>
<MudTd DataLabel="تاریخ">
<MudText Typo="Typo.caption">@context.DatePersian</MudText>
</MudTd>
<MudTd DataLabel="جزئیات">
<MudButton Size="Size.Small"
Variant="Variant.Text"
Color="Color.Info"
StartIcon="@Icons.Material.Filled.Visibility"
Href="@($"{RouteConstants.Commission.WeeklyBalance}?week={context.WeekNumber}")">
مشاهده
</MudButton>
</MudTd>
</RowTemplate>
</MudTable>
</MudPaper>
}
else
{
<MudAlert Severity="Severity.Info" Variant="Variant.Filled">
هنوز کمیسیونی دریافت نکرده‌اید.
</MudAlert>
}
</MudStack>
</MudContainer>
@@ -0,0 +1,65 @@
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace FrontOffice.Main.Pages.Commission;
public partial class CommissionHistoryPage : ComponentBase
{
[Inject] private CommissionService CommissionService { get; set; } = default!;
private List<CommissionPayoutDto> _payouts = new();
private bool _isLoading = true;
protected override async Task OnInitializedAsync()
{
await LoadAllPayoutsAsync();
}
private async Task LoadAllPayoutsAsync()
{
try
{
_isLoading = true;
// Load all payouts (large page size to get everything)
var result = await CommissionService.GetMyCommissionPayoutsAsync(
null,
string.Empty,
1,
1000
);
_payouts = result.Payouts;
}
catch (Exception ex)
{
Snackbar.Add($"خطا در دریافت تاریخچه: {ex.Message}", Severity.Error);
}
finally
{
_isLoading = false;
}
}
private string GetTotalEarnings()
{
var total = _payouts.Sum(p => p.TotalAmount);
return $"{total:N0} تومان";
}
private string GetAverageWeekly()
{
if (!_payouts.Any())
return "0 تومان";
var avg = _payouts.Average(p => p.TotalAmount);
return $"{avg:N0} تومان";
}
private Color GetStatusColor(string statusColor) => statusColor.ToLower() switch
{
"success" => Color.Success,
"warning" => Color.Warning,
"error" => Color.Error,
"info" => Color.Info,
_ => Color.Default
};
}
@@ -0,0 +1,154 @@
@attribute [Route(RouteConstants.Commission.WeeklyBalance)]
@using FrontOffice.Main.Utilities
@using MudBlazor
<PageTitle>تعادل هفتگی</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<MudStack Spacing="3">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h5">تعادل هفتگی کمیسیون</MudText>
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Commission.Dashboard">بازگشت</MudButton>
</MudStack>
<!-- انتخاب هفته -->
<MudPaper Elevation="2" Class="pa-3 rounded-lg">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.End">
<MudNumericField @bind-Value="_selectedWeekNumber"
Label="شماره هفته"
Variant="Variant.Outlined"
Min="1"
HelperText="هفته مورد نظر را انتخاب کنید (خالی = هفته جاری)"
Style="max-width: 200px;" />
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="LoadWeeklyBalanceAsync" StartIcon="@Icons.Material.Filled.Search">
مشاهده
</MudButton>
<MudButton Variant="Variant.Outlined" OnClick="LoadCurrentWeekAsync" StartIcon="@Icons.Material.Filled.Today">
هفته جاری
</MudButton>
</MudStack>
</MudPaper>
@if (_isLoading)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
}
else if (_weeklyBalance is not null)
{
<!-- اطلاعات هفته -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudStack Spacing="2">
<MudText Typo="Typo.h6">هفته @_weeklyBalance.WeekNumber - @_weeklyBalance.WeekLabel</MudText>
<MudDivider />
<MudGrid Spacing="2">
<MudItem xs="12" sm="6">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">تاریخ شروع:</MudText>
<MudText Typo="Typo.body1"><strong>@_weeklyBalance.StartDatePersian</strong></MudText>
</MudItem>
<MudItem xs="12" sm="6">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">تاریخ پایان:</MudText>
<MudText Typo="Typo.body1"><strong>@_weeklyBalance.EndDatePersian</strong></MudText>
</MudItem>
</MudGrid>
</MudStack>
</MudPaper>
<!-- تعادل چپ و راست -->
<MudGrid Spacing="2">
<MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;">
<MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Color="Color.Info" Size="Size.Large" />
<MudText Typo="Typo.h6" Color="Color.Info">شاخه چپ</MudText>
</MudStack>
<MudText Typo="Typo.h4" Color="Color.Info">@_weeklyBalance.LeftBalanceFormatted</MudText>
<MudProgressLinear Color="Color.Info"
Value="@GetLeftPercentage()"
Size="Size.Large"
Class="rounded-lg" />
<MudText Typo="Typo.caption" Class="mud-text-secondary">
@GetLeftPercentage().ToString("F1")% از کل تعادل
</MudText>
</MudStack>
</MudPaper>
</MudItem>
<MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;">
<MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.ChevronRight" Color="Color.Success" Size="Size.Large" />
<MudText Typo="Typo.h6" Color="Color.Success">شاخه راست</MudText>
</MudStack>
<MudText Typo="Typo.h4" Color="Color.Success">@_weeklyBalance.RightBalanceFormatted</MudText>
<MudProgressLinear Color="Color.Success"
Value="@GetRightPercentage()"
Size="Size.Large"
Class="rounded-lg" />
<MudText Typo="Typo.caption" Class="mud-text-secondary">
@GetRightPercentage().ToString("F1")% از کل تعادل
</MudText>
</MudStack>
</MudPaper>
</MudItem>
</MudGrid>
<!-- محاسبات کمیسیون -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">محاسبات کمیسیون</MudText>
<MudStack Spacing="2">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body1">تعادل چپ:</MudText>
<MudText Typo="Typo.body1" Color="Color.Info"><strong>@_weeklyBalance.LeftBalanceFormatted</strong></MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body1">تعادل راست:</MudText>
<MudText Typo="Typo.body1" Color="Color.Success"><strong>@_weeklyBalance.RightBalanceFormatted</strong></MudText>
</MudStack>
<MudDivider />
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body1">حداقل تعادل (پایه کمیسیون):</MudText>
<MudText Typo="Typo.h6" Color="Color.Warning"><strong>@_weeklyBalance.MinBalanceFormatted</strong></MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body1">تعداد تعادل (دفعات):</MudText>
<MudText Typo="Typo.h6" Color="Color.Secondary"><strong>@_weeklyBalance.BalanceCount</strong></MudText>
</MudStack>
<MudDivider />
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h6">کمیسیون محاسبه شده:</MudText>
<MudText Typo="Typo.h5" Color="Color.Primary"><strong>@_weeklyBalance.CalculatedCommissionFormatted</strong></MudText>
</MudStack>
@if (_weeklyBalance.LeftCarryover > 0 || _weeklyBalance.RightCarryover > 0)
{
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
<MudText Typo="Typo.body2">
<strong>Carryover (انتقال به هفته بعد):</strong><br />
چپ: @_weeklyBalance.LeftCarryoverFormatted | راست: @_weeklyBalance.RightCarryoverFormatted
</MudText>
</MudAlert>
}
</MudStack>
</MudPaper>
<!-- نمودار مقایسه -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">نمودار مقایسه</MudText>
<MudChart ChartType="ChartType.Bar"
Width="100%"
Height="300px"
InputData="@(new double[] { _weeklyBalance.LeftBalance, _weeklyBalance.RightBalance, _weeklyBalance.MinBalance })"
InputLabels="@(new[] { "چپ", "راست", "حداقل" })"
ChartOptions="@_chartOptions" />
</MudPaper>
}
else if (_hasError)
{
<MudAlert Severity="Severity.Error" Variant="Variant.Filled">
خطا در دریافت اطلاعات. لطفا دوباره تلاش کنید.
</MudAlert>
}
</MudStack>
</MudContainer>
@@ -0,0 +1,82 @@
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace FrontOffice.Main.Pages.Commission;
public partial class WeeklyBalancePage : ComponentBase
{
[Inject] private CommissionService CommissionService { get; set; } = default!;
[Parameter]
[SupplyParameterFromQuery(Name = "week")]
public int? QueryWeekNumber { get; set; }
private WeeklyBalanceDto? _weeklyBalance;
private int? _selectedWeekNumber;
private bool _isLoading = true;
private bool _hasError = false;
private readonly ChartOptions _chartOptions = new()
{
ChartPalette = new[]
{
"#2196F3", // Info (Left)
"#4CAF50", // Success (Right)
"#FF9800" // Warning (Min)
}
};
protected override async Task OnInitializedAsync()
{
if (QueryWeekNumber.HasValue)
_selectedWeekNumber = QueryWeekNumber;
await LoadWeeklyBalanceAsync();
}
private async Task LoadWeeklyBalanceAsync()
{
try
{
_isLoading = true;
_hasError = false;
_weeklyBalance = await CommissionService.GetMyWeeklyBalanceAsync(_selectedWeekNumber);
}
catch (Exception ex)
{
_hasError = true;
Snackbar.Add($"خطا در دریافت تعادل: {ex.Message}", Severity.Error);
}
finally
{
_isLoading = false;
}
}
private async Task LoadCurrentWeekAsync()
{
_selectedWeekNumber = null;
await LoadWeeklyBalanceAsync();
}
private double GetLeftPercentage()
{
if (_weeklyBalance is null)
return 0;
var total = _weeklyBalance.LeftBalance + _weeklyBalance.RightBalance;
if (total == 0)
return 0;
return (_weeklyBalance.LeftBalance / (double)total) * 100;
}
private double GetRightPercentage()
{
if (_weeklyBalance is null)
return 0;
var total = _weeklyBalance.LeftBalance + _weeklyBalance.RightBalance;
if (total == 0)
return 0;
return (_weeklyBalance.RightBalance / (double)total) * 100;
}
}