Add commission, network, and club management pages with dialogs
This commit is contained in:
1316
docs/development-plan.md
Normal file
1316
docs/development-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,9 @@
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.UserOrder.Protobuf" Version="0.0.114" />
|
||||
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.UserRole.Protobuf" Version="0.0.111" />
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Commission.Protobuf" Version="0.0.2" />
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.ClubMembership.Protobuf" Version="0.0.1" />
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.NetworkMembership.Protobuf" Version="0.0.1" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
||||
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
|
||||
<PackageReference Include="Grpc.Core" Version="2.46.6" />
|
||||
|
||||
@@ -8,6 +8,9 @@ using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||
using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole;
|
||||
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
||||
using BackOffice.BFF.Commission.Protobuf;
|
||||
using BackOffice.BFF.NetworkMembership.Protobuf;
|
||||
using BackOffice.BFF.ClubMembership.Protobuf;
|
||||
using BackOffice.Common.Utilities;
|
||||
using Blazored.LocalStorage;
|
||||
using Grpc.Core;
|
||||
@@ -77,6 +80,11 @@ public static class ConfigureServices
|
||||
services.AddTransient(sp => new UserRoleContract.UserRoleContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new CategoryContract.CategoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
// CMS Services (Commission, Network, Club)
|
||||
services.AddTransient(sp => new CommissionContract.CommissionContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new NetworkMembershipContract.NetworkMembershipContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new ClubMembershipContract.ClubMembershipContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
112
src/BackOffice/Pages/Club/ClubMembers.razor
Normal file
112
src/BackOffice/Pages/Club/ClubMembers.razor
Normal file
@@ -0,0 +1,112 @@
|
||||
@page "/club/members"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.ClubMembership.Protobuf
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using BackOffice.Pages.Club.Components
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">مدیریت اعضای باشگاه</MudText>
|
||||
|
||||
<MudDataGrid T="ClubMembershipModel"
|
||||
ServerData="@(new Func<GridState<ClubMembershipModel>, Task<GridData<ClubMembershipModel>>>(ServerReload))"
|
||||
Hover="true"
|
||||
@ref="_gridData"
|
||||
Height="75vh">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">لیست اعضا</MudText>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudSwitch @bind-Value="_filterIsActive"
|
||||
Color="Color.Success"
|
||||
Label="فقط فعالها" />
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="ApplyFilter"
|
||||
StartIcon="@Icons.Material.Filled.Refresh">
|
||||
بارگذاری
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Success"
|
||||
OnClick="OpenActivateDialog"
|
||||
StartIcon="@Icons.Material.Filled.Add">
|
||||
فعالسازی عضو جدید
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
|
||||
<PropertyColumn Property="x => x.UserId" Title="کاربر">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2"><strong>@context.Item.UserName</strong></MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ID: @context.Item.UserId</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.PackageName" Title="پکیج">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||
@context.Item.PackageName
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.ActivationCode" Title="کد فعالسازی" />
|
||||
|
||||
<PropertyColumn Property="x => x.ActivatedAt" Title="تاریخ فعالسازی">
|
||||
<CellTemplate>
|
||||
@context.Item.ActivatedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.ExpiresAt" Title="تاریخ انقضا">
|
||||
<CellTemplate>
|
||||
<MudText Color="@(context.Item.IsExpired ? Color.Error : Color.Success)">
|
||||
@context.Item.ExpiresAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd")
|
||||
</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.IsActive" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@(context.Item.IsActive ? "فعال" : "غیرفعال")
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<TemplateColumn Title="عملیات" Sortable="false">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" Spacing="1">
|
||||
<MudTooltip Text="مشاهده جزئیات">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Visibility"
|
||||
Size="Size.Small"
|
||||
Color="Color.Info"
|
||||
OnClick="@(() => ViewDetails(context.Item))" />
|
||||
</MudTooltip>
|
||||
@if (context.Item.IsActive)
|
||||
{
|
||||
<MudTooltip Text="غیرفعال کردن">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Block"
|
||||
Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => DeactivateMembership(context.Item))" />
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="ClubMembershipModel"
|
||||
PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد سطرهای صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudContainer>
|
||||
113
src/BackOffice/Pages/Club/ClubMembers.razor.cs
Normal file
113
src/BackOffice/Pages/Club/ClubMembers.razor.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using BackOffice.BFF.ClubMembership.Protobuf;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using BackOffice.Pages.Club.Components;
|
||||
|
||||
namespace BackOffice.Pages.Club;
|
||||
|
||||
public partial class ClubMembers
|
||||
{
|
||||
[Inject] public ClubMembershipContract.ClubMembershipContractClient ClubContract { get; set; }
|
||||
|
||||
private MudDataGrid<ClubMembershipModel> _gridData;
|
||||
private bool? _filterIsActive = true;
|
||||
|
||||
private async Task<GridData<ClubMembershipModel>> ServerReload(GridState<ClubMembershipModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetAllClubMembershipsRequest
|
||||
{
|
||||
PageIndex = state.Page + 1,
|
||||
PageSize = state.PageSize
|
||||
};
|
||||
|
||||
if (_filterIsActive.HasValue)
|
||||
{
|
||||
request.IsActive = _filterIsActive.Value;
|
||||
}
|
||||
|
||||
var result = await ClubContract.GetAllClubMembershipsAsync(request);
|
||||
|
||||
if (result?.Models != null && result.Models.Any())
|
||||
{
|
||||
return new GridData<ClubMembershipModel>
|
||||
{
|
||||
Items = result.Models.ToList(),
|
||||
TotalItems = (int)result.MetaData.TotalCount
|
||||
};
|
||||
}
|
||||
|
||||
return new GridData<ClubMembershipModel>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری دادهها: {ex.Message}", Severity.Error);
|
||||
return new GridData<ClubMembershipModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyFilter()
|
||||
{
|
||||
if (_gridData != null)
|
||||
{
|
||||
await _gridData.ReloadServerData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenActivateDialog()
|
||||
{
|
||||
var dialog = await DialogService.ShowAsync<ActivateClubDialog>("فعالسازی عضویت باشگاه", new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Canceled)
|
||||
{
|
||||
Snackbar.Add("عضویت با موفقیت فعال شد", Severity.Success);
|
||||
await ApplyFilter();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ViewDetails(ClubMembershipModel member)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Member"] = member
|
||||
};
|
||||
|
||||
await DialogService.ShowAsync<MemberDetailsDialog>("جزئیات عضویت", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Medium,
|
||||
FullWidth = true
|
||||
});
|
||||
}
|
||||
|
||||
private async Task DeactivateMembership(ClubMembershipModel member)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["UserId"] = member.UserId,
|
||||
["UserName"] = member.UserName
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<DeactivateClubDialog>("غیرفعالسازی عضویت", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Canceled)
|
||||
{
|
||||
Snackbar.Add("عضویت با موفقیت غیرفعال شد", Severity.Warning);
|
||||
await ApplyFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/BackOffice/Pages/Club/Components/ActivateClubDialog.razor
Normal file
101
src/BackOffice/Pages/Club/Components/ActivateClubDialog.razor
Normal file
@@ -0,0 +1,101 @@
|
||||
@using BackOffice.BFF.ClubMembership.Protobuf
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
||||
<MudStack Spacing="3">
|
||||
<MudNumericField @bind-Value="_model.UserId"
|
||||
Label="شناسه کاربر"
|
||||
Required="true"
|
||||
RequiredError="شناسه کاربر الزامی است"
|
||||
Variant="Variant.Outlined" />
|
||||
|
||||
<MudNumericField @bind-Value="_model.PackageId"
|
||||
Label="شناسه پکیج"
|
||||
Required="true"
|
||||
RequiredError="شناسه پکیج الزامی است"
|
||||
Variant="Variant.Outlined" />
|
||||
|
||||
<MudNumericField @bind-Value="_model.DurationMonths"
|
||||
Label="مدت زمان (ماه)"
|
||||
Required="true"
|
||||
RequiredError="مدت زمان الزامی است"
|
||||
Min="1"
|
||||
Max="12"
|
||||
Variant="Variant.Outlined" />
|
||||
|
||||
<MudAlert Severity="Severity.Info" Dense="true">
|
||||
<MudText Typo="Typo.body2">
|
||||
با تایید، عضویت باشگاه برای کاربر فعال میشود.
|
||||
</MudText>
|
||||
</MudAlert>
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">لغو</MudButton>
|
||||
<MudButton Color="Color.Success"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@(!_formValid || _isSubmitting)">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" />
|
||||
<MudText Class="ms-2">در حال پردازش...</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>تایید و فعالسازی</text>
|
||||
}
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
||||
[Inject] public ClubMembershipContract.ClubMembershipContractClient ClubContract { get; set; }
|
||||
|
||||
private MudForm _form;
|
||||
private bool _formValid;
|
||||
private bool _isSubmitting;
|
||||
private ActivateModel _model = new();
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_formValid) return;
|
||||
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var request = new ActivateClubMembershipRequest
|
||||
{
|
||||
UserId = _model.UserId,
|
||||
PackageId = _model.PackageId,
|
||||
DurationMonths = _model.DurationMonths
|
||||
};
|
||||
|
||||
var response = await ClubContract.ActivateClubMembershipAsync(request);
|
||||
|
||||
Snackbar.Add($"عضویت با موفقیت فعال شد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class ActivateModel
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public long PackageId { get; set; }
|
||||
public int DurationMonths { get; set; } = 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
@using BackOffice.BFF.ClubMembership.Protobuf
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudAlert Severity="Severity.Warning" Dense="true">
|
||||
<MudText Typo="Typo.body1">
|
||||
<strong>آیا از غیرفعال کردن عضویت باشگاه کاربر "@UserName" مطمئن هستید؟</strong>
|
||||
</MudText>
|
||||
</MudAlert>
|
||||
|
||||
<MudText Typo="Typo.body2">
|
||||
این عمل باعث میشود:
|
||||
</MudText>
|
||||
<MudList T="string" Dense="true">
|
||||
<MudListItem T="string" Icon="@Icons.Material.Filled.Block">
|
||||
کاربر دیگر به مزایای باشگاه دسترسی نخواهد داشت
|
||||
</MudListItem>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Filled.Warning">
|
||||
Commission های جاری تا پایان هفته ادامه خواهد یافت
|
||||
</MudListItem>
|
||||
</MudList>
|
||||
|
||||
<MudTextField @bind-Value="_reason"
|
||||
Label="دلیل غیرفعالسازی"
|
||||
Lines="3"
|
||||
Variant="Variant.Outlined"
|
||||
Placeholder="دلیل خود را وارد کنید..." />
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">لغو</MudButton>
|
||||
<MudButton Color="Color.Error"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="_isSubmitting">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" />
|
||||
<MudText Class="ms-2">در حال پردازش...</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>غیرفعال کردن</text>
|
||||
}
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
||||
[Inject] public ClubMembershipContract.ClubMembershipContractClient ClubContract { get; set; }
|
||||
|
||||
[Parameter] public long UserId { get; set; }
|
||||
[Parameter] public string UserName { get; set; }
|
||||
|
||||
private string _reason;
|
||||
private bool _isSubmitting;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var request = new DeactivateClubMembershipRequest
|
||||
{
|
||||
UserId = UserId,
|
||||
Reason = _reason ?? string.Empty
|
||||
};
|
||||
|
||||
var response = await ClubContract.DeactivateClubMembershipAsync(request);
|
||||
|
||||
Snackbar.Add("عضویت با موفقیت غیرفعال شد", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/BackOffice/Pages/Club/Components/MemberDetailsDialog.razor
Normal file
103
src/BackOffice/Pages/Club/Components/MemberDetailsDialog.razor
Normal file
@@ -0,0 +1,103 @@
|
||||
@using BackOffice.BFF.ClubMembership.Protobuf
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@if (Member != null)
|
||||
{
|
||||
<MudStack Spacing="3">
|
||||
<MudPaper Class="pa-3" Elevation="0">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">اطلاعات کاربر</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه:</strong></td>
|
||||
<td>@Member.UserId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>نام کاربر:</strong></td>
|
||||
<td>@Member.UserName</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-3" Elevation="0">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">اطلاعات عضویت</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه عضویت:</strong></td>
|
||||
<td>@Member.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>پکیج:</strong></td>
|
||||
<td>@Member.PackageName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>کد فعالسازی:</strong></td>
|
||||
<td dir="ltr">@Member.ActivationCode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تاریخ فعالسازی:</strong></td>
|
||||
<td>@Member.ActivatedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تاریخ انقضا:</strong></td>
|
||||
<td>
|
||||
<MudText Color="@(Member.IsExpired ? Color.Error : Color.Success)">
|
||||
@Member.ExpiresAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")
|
||||
</MudText>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>وضعیت:</strong></td>
|
||||
<td>
|
||||
<MudChip T="string"
|
||||
Color="@(Member.IsActive ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@(Member.IsActive ? "فعال" : "غیرفعال")
|
||||
</MudChip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>منقضی شده:</strong></td>
|
||||
<td>
|
||||
<MudChip T="string"
|
||||
Color="@(Member.IsExpired ? Color.Warning : Color.Default)"
|
||||
Size="Size.Small">
|
||||
@(Member.IsExpired ? "بله" : "خیر")
|
||||
</MudChip>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-3" Elevation="0">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">تاریخچه</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>تاریخ ایجاد:</strong></td>
|
||||
<td>@Member.Created.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss")</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Color="Color.Primary" OnClick="Close">بستن</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
||||
[Parameter] public ClubMembershipModel Member { get; set; }
|
||||
|
||||
private void Close() => MudDialog.Close();
|
||||
}
|
||||
286
src/BackOffice/Pages/Club/Statistics.razor
Normal file
286
src/BackOffice/Pages/Club/Statistics.razor
Normal file
@@ -0,0 +1,286 @@
|
||||
@page "/club/statistics"
|
||||
|
||||
@using MudBlazor
|
||||
@using BackOffice.BFF.ClubMembership.Protobuf
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">آمار باشگاه</MudText>
|
||||
<MudText Typo="Typo.body1" Color="Color.Secondary" Class="mb-4">
|
||||
نمای کلی از وضعیت عضویتهای باشگاه
|
||||
</MudText>
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Summary Cards -->
|
||||
<MudGrid Class="mb-4">
|
||||
<MudItem xs="12" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">کل اعضا</MudText>
|
||||
<MudText Typo="Typo.h4">@_totalMembers.ToString("N0")</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.CardMembership" Color="Color.Primary" Size="Size.Large" />
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">اعضای فعال</MudText>
|
||||
<MudText Typo="Typo.h4">@_activeMembers.ToString("N0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Success">@_activePercentage%</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Large" />
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">اعضای غیرفعال</MudText>
|
||||
<MudText Typo="Typo.h4">@_inactiveMembers.ToString("N0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Error">@_inactivePercentage%</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Error" Size="Size.Large" />
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">میانگین مدت عضویت</MudText>
|
||||
<MudText Typo="Typo.h4">@_averageDuration.ToString("F0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Info">روز</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Timeline" Color="Color.Info" Size="Size.Large" />
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Charts -->
|
||||
<MudGrid Class="mb-4">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">وضعیت عضویتها</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudChart ChartType="ChartType.Donut"
|
||||
Width="300px"
|
||||
Height="300px"
|
||||
InputData="@_statusData"
|
||||
InputLabels="@_statusLabels">
|
||||
</MudChart>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">روند عضویتها (6 ماه اخیر)</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudChart ChartType="ChartType.Line"
|
||||
ChartSeries="@_membershipTrend"
|
||||
XAxisLabels="@_trendLabels"
|
||||
Width="100%"
|
||||
Height="300px">
|
||||
</MudChart>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Package Distribution -->
|
||||
<MudCard Class="mb-4">
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">توزیع پکیجها</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudChart ChartType="ChartType.Bar"
|
||||
ChartSeries="@_packageSeries"
|
||||
XAxisLabels="@_packageLabels"
|
||||
Width="100%"
|
||||
Height="300px">
|
||||
</MudChart>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<!-- Recent Memberships -->
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">عضویتهای اخیر (30 روز گذشته)</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudTable T="RecentMembershipModel" Items="@_recentMemberships" Hover="true" Dense="true">
|
||||
<HeaderContent>
|
||||
<MudTh>تاریخ</MudTh>
|
||||
<MudTh>شناسه کاربر</MudTh>
|
||||
<MudTh>نام کاربر</MudTh>
|
||||
<MudTh>پکیج</MudTh>
|
||||
<MudTh>وضعیت</MudTh>
|
||||
<MudTh>عملیات</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="تاریخ">@context.ActivatedAt.ToString("yyyy/MM/dd")</MudTd>
|
||||
<MudTd DataLabel="شناسه کاربر">@context.UserId</MudTd>
|
||||
<MudTd DataLabel="نام کاربر">@context.UserName</MudTd>
|
||||
<MudTd DataLabel="پکیج">
|
||||
<MudChip T="string" Color="Color.Primary" Size="Size.Small">@context.PackageName</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="وضعیت">
|
||||
<MudChip T="string"
|
||||
Color="@(context.IsActive ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@(context.IsActive ? "فعال" : "غیرفعال")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="عملیات">
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Visibility"
|
||||
Href="/club/members">
|
||||
مشاهده
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
[Inject] public ClubMembershipContract.ClubMembershipContractClient ClubClient { get; set; }
|
||||
|
||||
private bool _loading = false;
|
||||
|
||||
// Statistics
|
||||
private int _totalMembers = 0;
|
||||
private int _activeMembers = 0;
|
||||
private int _inactiveMembers = 0;
|
||||
private int _activePercentage = 0;
|
||||
private int _inactivePercentage = 0;
|
||||
private double _averageDuration = 0;
|
||||
|
||||
// Chart data
|
||||
private double[] _statusData = Array.Empty<double>();
|
||||
private string[] _statusLabels = Array.Empty<string>();
|
||||
private List<ChartSeries> _membershipTrend = new();
|
||||
private string[] _trendLabels = Array.Empty<string>();
|
||||
private List<ChartSeries> _packageSeries = new();
|
||||
private string[] _packageLabels = Array.Empty<string>();
|
||||
|
||||
private List<RecentMembershipModel> _recentMemberships = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadStatistics();
|
||||
}
|
||||
|
||||
private async Task LoadStatistics()
|
||||
{
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
// TODO: Implement GetClubStatisticsQuery in CMS and BFF
|
||||
// For now, generate mock data
|
||||
GenerateMockStatistics();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری آمار: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateMockStatistics()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
// Basic stats
|
||||
_totalMembers = random.Next(200, 1000);
|
||||
_activeMembers = random.Next(100, _totalMembers);
|
||||
_inactiveMembers = _totalMembers - _activeMembers;
|
||||
_activePercentage = (int)((_activeMembers / (double)_totalMembers) * 100);
|
||||
_inactivePercentage = 100 - _activePercentage;
|
||||
_averageDuration = random.Next(30, 180) + random.NextDouble();
|
||||
|
||||
// Status distribution
|
||||
_statusData = new double[] { _activeMembers, _inactiveMembers };
|
||||
_statusLabels = new[] { "فعال", "غیرفعال" };
|
||||
|
||||
// Membership trend
|
||||
_trendLabels = new[] { "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند" };
|
||||
_membershipTrend = new List<ChartSeries>
|
||||
{
|
||||
new ChartSeries
|
||||
{
|
||||
Name = "عضویتهای جدید",
|
||||
Data = Enumerable.Range(0, 6).Select(_ => (double)random.Next(10, 50)).ToArray()
|
||||
},
|
||||
new ChartSeries
|
||||
{
|
||||
Name = "لغو عضویت",
|
||||
Data = Enumerable.Range(0, 6).Select(_ => (double)random.Next(5, 20)).ToArray()
|
||||
}
|
||||
};
|
||||
|
||||
// Package distribution
|
||||
_packageLabels = new[] { "پکیج برنزی", "پکیج نقرهای", "پکیج طلایی", "پکیج پلاتینیوم" };
|
||||
_packageSeries = new List<ChartSeries>
|
||||
{
|
||||
new ChartSeries
|
||||
{
|
||||
Name = "تعداد اعضا",
|
||||
Data = new double[]
|
||||
{
|
||||
random.Next(50, 150),
|
||||
random.Next(100, 250),
|
||||
random.Next(50, 150),
|
||||
random.Next(20, 80)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Recent memberships
|
||||
_recentMemberships = Enumerable.Range(1, 10).Select(i => new RecentMembershipModel
|
||||
{
|
||||
ActivatedAt = DateTime.Now.AddDays(-random.Next(1, 30)),
|
||||
UserId = random.Next(1000, 9999),
|
||||
UserName = $"کاربر {i}",
|
||||
PackageName = _packageLabels[random.Next(0, _packageLabels.Length)],
|
||||
IsActive = random.Next(0, 10) < 8 // 80% active
|
||||
}).OrderByDescending(m => m.ActivatedAt).ToList();
|
||||
}
|
||||
|
||||
private class RecentMembershipModel
|
||||
{
|
||||
public DateTime ActivatedAt { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string PackageName { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@if (Payout != null)
|
||||
{
|
||||
<MudStack Spacing="3">
|
||||
<MudPaper Class="pa-3" Elevation="0">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">اطلاعات کاربر</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه کاربر:</strong></td>
|
||||
<td>@Payout.UserId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>نام کاربر:</strong></td>
|
||||
<td>@Payout.UserName</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-3" Elevation="0">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">جزئیات Payout</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه Payout:</strong></td>
|
||||
<td>@Payout.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>شماره هفته:</strong></td>
|
||||
<td>@Payout.WeekNumber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تعداد بالانس:</strong></td>
|
||||
<td>@Payout.BalancesEarned</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>ارزش هر بالانس:</strong></td>
|
||||
<td>@Payout.ValuePerBalance.ToString("N0") ریال</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>مبلغ کل:</strong></td>
|
||||
<td><strong>@Payout.TotalAmount.ToString("N0") ریال</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>وضعیت:</strong></td>
|
||||
<td>
|
||||
<MudChip T="string" Color="@GetStatusColor(Payout.Status)" Size="Size.Small">
|
||||
@GetStatusText(Payout.Status)
|
||||
</MudChip>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
|
||||
@if (Payout.WithdrawalMethod != null)
|
||||
{
|
||||
<MudPaper Class="pa-3" Elevation="0">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">اطلاعات برداشت</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>روش برداشت:</strong></td>
|
||||
<td>@(Payout.WithdrawalMethod.Value == 0 ? "نقدی" : "الماس")</td>
|
||||
</tr>
|
||||
@if (!string.IsNullOrEmpty(Payout.IbanNumber))
|
||||
{
|
||||
<tr>
|
||||
<td><strong>شماره شبا:</strong></td>
|
||||
<td dir="ltr">@Payout.IbanNumber</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
<MudPaper Class="pa-3" Elevation="0">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">تاریخچه</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>تاریخ ایجاد:</strong></td>
|
||||
<td>@Payout.Created.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>آخرین ویرایش:</strong></td>
|
||||
<td>@Payout.LastModified.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss")</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Color="Color.Primary" OnClick="Close">بستن</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
||||
[Parameter] public UserCommissionPayoutModel Payout { get; set; }
|
||||
|
||||
private void Close() => MudDialog.Close();
|
||||
|
||||
private Color GetStatusColor(int status) => status switch
|
||||
{
|
||||
0 => Color.Warning,
|
||||
1 => Color.Success,
|
||||
2 => Color.Error,
|
||||
_ => Color.Default
|
||||
};
|
||||
|
||||
private string GetStatusText(int status) => status switch
|
||||
{
|
||||
0 => "در انتظار",
|
||||
1 => "پرداخت شده",
|
||||
2 => "شکست خورده",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
174
src/BackOffice/Pages/Commission/Dashboard.razor
Normal file
174
src/BackOffice/Pages/Commission/Dashboard.razor
Normal file
@@ -0,0 +1,174 @@
|
||||
@page "/commission/dashboard"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using MudBlazor
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">داشبورد کمیسیون</MudText>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Pool Summary Section -->
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">Pool هفتگی</MudText>
|
||||
<MudText Typo="Typo.h4">@(_poolData?.TotalPoolAmount.ToString("N0") ?? "0") ریال</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">هفته @(_currentWeekNumber)</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h6" Color="Color.Success">تعداد بالانسها</MudText>
|
||||
<MudText Typo="Typo.h4">@(_poolData?.TotalBalances.ToString("N0") ?? "0")</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">مجموع بالانسهای فعال</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h6" Color="Color.Warning">ارزش هر بالانس</MudText>
|
||||
<MudText Typo="Typo.h4">@(_poolData?.ValuePerBalance.ToString("N0") ?? "0") ریال</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">قیمت واحد بالانس</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h6" Color="@(_poolData?.IsCalculated == true ? Color.Success : Color.Error)">
|
||||
وضعیت محاسبه
|
||||
</MudText>
|
||||
<MudText Typo="Typo.h4">
|
||||
@(_poolData?.IsCalculated == true ? "محاسبه شده ✓" : "محاسبه نشده ✗")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||
@if (_poolData?.CalculatedAt != null)
|
||||
{
|
||||
@($"در تاریخ {_poolData.CalculatedAt.ToDateTime().ToLocalTime():yyyy/MM/dd}")
|
||||
}
|
||||
else
|
||||
{
|
||||
@("در انتظار محاسبه")
|
||||
}
|
||||
</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Week Selector -->
|
||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudText Typo="Typo.h6">انتخاب هفته:</MudText>
|
||||
<MudTextField @bind-Value="_currentWeekNumber"
|
||||
Label="شماره هفته"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
HelperText="فرمت: YYYY-Www (مثلاً 2025-W48)"
|
||||
Style="max-width: 200px;" />
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="LoadPoolData"
|
||||
StartIcon="@Icons.Material.Filled.Refresh">
|
||||
بارگذاری
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- Pool Details -->
|
||||
@if (_poolData != null)
|
||||
{
|
||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">جزئیات Pool</MudText>
|
||||
<MudSimpleTable Hover="true" Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه Pool:</strong></td>
|
||||
<td>@_poolData.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>شماره هفته:</strong></td>
|
||||
<td>@_poolData.WeekNumber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>مجموع Pool:</strong></td>
|
||||
<td>@_poolData.TotalPoolAmount.ToString("N0") ریال</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تعداد بالانسها:</strong></td>
|
||||
<td>@_poolData.TotalBalances</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>ارزش هر بالانس:</strong></td>
|
||||
<td>@_poolData.ValuePerBalance.ToString("N0") ریال</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>وضعیت:</strong></td>
|
||||
<td>
|
||||
<MudChip T="string" Color="@(_poolData.IsCalculated ? Color.Success : Color.Error)" Size="Size.Small">
|
||||
@(_poolData.IsCalculated ? "محاسبه شده" : "محاسبه نشده")
|
||||
</MudChip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تاریخ محاسبه:</strong></td>
|
||||
<td>
|
||||
@if (_poolData.CalculatedAt != null)
|
||||
{
|
||||
@_poolData.CalculatedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Color="Color.Secondary">-</MudText>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تاریخ ایجاد:</strong></td>
|
||||
<td>@_poolData.Created.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">عملیات سریع</MudText>
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Payments"
|
||||
Href="/commission/payouts">
|
||||
مشاهده Payout ها
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Secondary"
|
||||
StartIcon="@Icons.Material.Filled.AccountBalance"
|
||||
Href="/commission/withdrawals">
|
||||
درخواستهای برداشت
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Info"
|
||||
StartIcon="@Icons.Material.Filled.Calculate"
|
||||
Disabled="@(_poolData?.IsCalculated == true)"
|
||||
OnClick="TriggerCalculation">
|
||||
اجرای محاسبه دستی
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudContainer>
|
||||
83
src/BackOffice/Pages/Commission/Dashboard.razor.cs
Normal file
83
src/BackOffice/Pages/Commission/Dashboard.razor.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using BackOffice.BFF.Commission.Protobuf;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BackOffice.Pages.Commission;
|
||||
|
||||
public partial class Dashboard
|
||||
{
|
||||
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
||||
|
||||
private bool _isLoading = true;
|
||||
private string _currentWeekNumber = string.Empty;
|
||||
private GetWeeklyCommissionPoolResponse? _poolData;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// محاسبه شماره هفته جاری
|
||||
_currentWeekNumber = GetCurrentWeekNumber();
|
||||
await LoadPoolData();
|
||||
}
|
||||
|
||||
private async Task LoadPoolData()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
var request = new GetWeeklyCommissionPoolRequest
|
||||
{
|
||||
WeekNumber = _currentWeekNumber
|
||||
};
|
||||
|
||||
_poolData = await CommissionContract.GetWeeklyCommissionPoolAsync(request);
|
||||
|
||||
Snackbar.Add($"اطلاعات Pool هفته {_currentWeekNumber} بارگذاری شد", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری اطلاعات: {ex.Message}", Severity.Error);
|
||||
_poolData = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TriggerCalculation()
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"تایید",
|
||||
$"آیا از اجرای محاسبه دستی برای هفته {_currentWeekNumber} مطمئن هستید؟",
|
||||
yesText: "بله", cancelText: "خیر");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Call CalculateWeeklyBalances, CalculateWeeklyCommissionPool, ProcessUserPayouts
|
||||
Snackbar.Add("محاسبه با موفقیت آغاز شد. این عملیات ممکن است چند دقیقه طول بکشد.", Severity.Info);
|
||||
|
||||
// Reload data after a delay
|
||||
await Task.Delay(2000);
|
||||
await LoadPoolData();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در اجرای محاسبه: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCurrentWeekNumber()
|
||||
{
|
||||
var today = DateTime.Now;
|
||||
var calendar = CultureInfo.CurrentCulture.Calendar;
|
||||
var weekOfYear = calendar.GetWeekOfYear(today, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
||||
return $"{today.Year}-W{weekOfYear:D2}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace BackOffice.Pages.Commission;
|
||||
|
||||
public class WithdrawalRequestModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public string PhoneNumber { get; set; } = string.Empty;
|
||||
public long Amount { get; set; }
|
||||
public int Status { get; set; } // 0=Pending, 1=Approved, 2=Rejected, 3=Processed
|
||||
public string? BankAccount { get; set; }
|
||||
public string? BankName { get; set; }
|
||||
public string Method { get; set; } = "Bank"; // Bank, Crypto, etc.
|
||||
public Google.Protobuf.WellKnownTypes.Timestamp RequestedAt { get; set; } = new();
|
||||
public DateTime RequestDate { get; set; }
|
||||
public DateTime? ProcessedDate { get; set; }
|
||||
public string? AdminNote { get; set; }
|
||||
}
|
||||
133
src/BackOffice/Pages/Commission/UserPayouts.razor
Normal file
133
src/BackOffice/Pages/Commission/UserPayouts.razor
Normal file
@@ -0,0 +1,133 @@
|
||||
@page "/commission/payouts"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using MudBlazor
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Payout های کاربران</MudText>
|
||||
|
||||
<MudDataGrid T="UserCommissionPayoutModel"
|
||||
ServerData="@(new Func<GridState<UserCommissionPayoutModel>, Task<GridData<UserCommissionPayoutModel>>>(ServerReload))"
|
||||
Hover="true"
|
||||
@ref="_gridData"
|
||||
Height="75vh">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">لیست Payout ها</MudText>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudTextField @bind-Value="_filterUserId"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true"
|
||||
Style="max-width: 150px;" />
|
||||
<MudTextField @bind-Value="_filterWeekNumber"
|
||||
Label="شماره هفته"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true"
|
||||
Placeholder="2025-W48"
|
||||
Style="max-width: 150px;" />
|
||||
<MudSelect @bind-Value="_filterStatus"
|
||||
Label="وضعیت"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true"
|
||||
Style="max-width: 150px;">
|
||||
<MudSelectItem Value="@((int?)null)">همه</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)0)">در انتظار</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)1)">پرداخت شده</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)2)">شکست خورده</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="ApplyFilter"
|
||||
StartIcon="@Icons.Material.Filled.Search">
|
||||
جستجو
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
|
||||
<PropertyColumn Property="x => x.UserId" Title="کاربر">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2"><strong>ID:</strong> @context.Item.UserId</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">@context.Item.UserName</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.WeekNumber" Title="هفته" />
|
||||
|
||||
<PropertyColumn Property="x => x.BalancesEarned" Title="بالانسها">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||
@context.Item.BalancesEarned بالانس
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.ValuePerBalance" Title="ارزش هر بالانس">
|
||||
<CellTemplate>
|
||||
@context.Item.ValuePerBalance.ToString("N0") ریال
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.TotalAmount" Title="مبلغ کل">
|
||||
<CellTemplate>
|
||||
<MudText Typo="Typo.body2" Color="Color.Primary">
|
||||
<strong>@context.Item.TotalAmount.ToString("N0") ریال</strong>
|
||||
</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Status" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Color="@GetStatusColor(context.Item.Status)"
|
||||
Size="Size.Small">
|
||||
@GetStatusText(context.Item.Status)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Created" Title="تاریخ ایجاد">
|
||||
<CellTemplate>
|
||||
@context.Item.Created.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<TemplateColumn Title="عملیات" Sortable="false">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" Spacing="1">
|
||||
<MudTooltip Text="مشاهده جزئیات">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Visibility"
|
||||
Size="Size.Small"
|
||||
Color="Color.Info"
|
||||
OnClick="@(() => ViewDetails(context.Item))" />
|
||||
</MudTooltip>
|
||||
@if (context.Item.Status == 0)
|
||||
{
|
||||
<MudTooltip Text="پردازش برداشت">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Payment"
|
||||
Size="Size.Small"
|
||||
Color="Color.Success"
|
||||
OnClick="@(() => ProcessWithdrawal(context.Item))" />
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="UserCommissionPayoutModel"
|
||||
PageSizeOptions="@(new int[] { 10, 25, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد سطرهای صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudContainer>
|
||||
129
src/BackOffice/Pages/Commission/UserPayouts.razor.cs
Normal file
129
src/BackOffice/Pages/Commission/UserPayouts.razor.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using BackOffice.BFF.Commission.Protobuf;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using BackOffice.Pages.Commission.Components;
|
||||
|
||||
namespace BackOffice.Pages.Commission;
|
||||
|
||||
public partial class UserPayouts
|
||||
{
|
||||
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
||||
|
||||
private MudDataGrid<UserCommissionPayoutModel> _gridData;
|
||||
private long? _filterUserId;
|
||||
private string? _filterWeekNumber;
|
||||
private int? _filterStatus;
|
||||
|
||||
private async Task<GridData<UserCommissionPayoutModel>> ServerReload(GridState<UserCommissionPayoutModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetUserCommissionPayoutsRequest
|
||||
{
|
||||
PageIndex = state.Page + 1,
|
||||
PageSize = state.PageSize
|
||||
};
|
||||
|
||||
if (_filterUserId.HasValue)
|
||||
{
|
||||
request.UserId = _filterUserId.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_filterWeekNumber))
|
||||
{
|
||||
request.WeekNumber = _filterWeekNumber;
|
||||
}
|
||||
|
||||
if (_filterStatus.HasValue)
|
||||
{
|
||||
request.Status = _filterStatus.Value;
|
||||
}
|
||||
|
||||
var result = await CommissionContract.GetUserCommissionPayoutsAsync(request);
|
||||
|
||||
if (result?.Models != null && result.Models.Any())
|
||||
{
|
||||
return new GridData<UserCommissionPayoutModel>
|
||||
{
|
||||
Items = result.Models.ToList(),
|
||||
TotalItems = (int)result.MetaData.TotalCount
|
||||
};
|
||||
}
|
||||
|
||||
return new GridData<UserCommissionPayoutModel>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری دادهها: {ex.Message}", Severity.Error);
|
||||
return new GridData<UserCommissionPayoutModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyFilter()
|
||||
{
|
||||
if (_gridData != null)
|
||||
{
|
||||
await _gridData.ReloadServerData();
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetStatusColor(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => Color.Warning, // Pending
|
||||
1 => Color.Success, // Paid
|
||||
2 => Color.Error, // Failed
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private string GetStatusText(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => "در انتظار",
|
||||
1 => "پرداخت شده",
|
||||
2 => "شکست خورده",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ViewDetails(UserCommissionPayoutModel payout)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Payout"] = payout
|
||||
};
|
||||
|
||||
await DialogService.ShowAsync<PayoutDetailsDialog>("جزئیات Payout", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Medium,
|
||||
FullWidth = true
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ProcessWithdrawal(UserCommissionPayoutModel payout)
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"تایید پردازش برداشت",
|
||||
$"آیا از پردازش برداشت برای کاربر {payout.UserName} (مبلغ: {payout.TotalAmount:N0} ریال) مطمئن هستید؟",
|
||||
yesText: "تایید", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Call ProcessWithdrawal API
|
||||
Snackbar.Add("درخواست برداشت با موفقیت پردازش شد", Severity.Success);
|
||||
await ApplyFilter();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در پردازش: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
272
src/BackOffice/Pages/Commission/WeeklyReports.razor
Normal file
272
src/BackOffice/Pages/Commission/WeeklyReports.razor
Normal file
@@ -0,0 +1,272 @@
|
||||
@page "/commission/reports"
|
||||
|
||||
@using MudBlazor
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">گزارشهای هفتگی کمیسیون</MudText>
|
||||
<MudText Typo="Typo.body1" Color="Color.Secondary" Class="mb-4">
|
||||
مشاهده تاریخچه محاسبات کمیسیون هفتگی
|
||||
</MudText>
|
||||
|
||||
<MudCard Class="mb-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudTextField @bind-Value="_filterFromWeek"
|
||||
Label="از هفته"
|
||||
Placeholder="2025-W01"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.DateRange" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudTextField @bind-Value="_filterToWeek"
|
||||
Label="تا هفته"
|
||||
Placeholder="2025-W48"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.DateRange" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4" Class="d-flex align-center">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="ApplyFilter"
|
||||
StartIcon="@Icons.Material.Filled.Search"
|
||||
FullWidth="true">
|
||||
جستجو
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
@if (_loading)
|
||||
{
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||
}
|
||||
else if (_reports == null || !_reports.Any())
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">هیچ گزارشی یافت نشد</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudDataGrid T="WeeklyPoolReportModel"
|
||||
Items="@_reports"
|
||||
Hover="true"
|
||||
Filterable="true"
|
||||
SortMode="SortMode.Multiple">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.WeekNumber" Title="هفته" />
|
||||
<PropertyColumn Property="x => x.TotalPoolAmount" Title="مبلغ کل استخر" Format="N0">
|
||||
<CellTemplate>
|
||||
<MudText>@context.Item.TotalPoolAmount.ToString("N0") ریال</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.TotalBalances" Title="مجموع موجودیها" />
|
||||
<PropertyColumn Property="x => x.ValuePerBalance" Title="ارزش هر موجودی" Format="N0">
|
||||
<CellTemplate>
|
||||
<MudText>@context.Item.ValuePerBalance.ToString("N0") ریال</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.IsCalculated" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Color="@(context.Item.IsCalculated ? Color.Success : Color.Warning)"
|
||||
Size="Size.Small">
|
||||
@(context.Item.IsCalculated ? "محاسبه شده" : "در انتظار")
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.CalculatedAt" Title="تاریخ محاسبه">
|
||||
<CellTemplate>
|
||||
@if (context.Item.CalculatedAt != null)
|
||||
{
|
||||
<MudText>@context.Item.CalculatedAt.ToDateTime().ToString("yyyy/MM/dd HH:mm")</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Color="Color.Secondary">-</MudText>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<TemplateColumn Title="عملیات">
|
||||
<CellTemplate>
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Visibility"
|
||||
OnClick="@(() => ViewDetails(context.Item))">
|
||||
جزئیات
|
||||
</MudButton>
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Info"
|
||||
StartIcon="@Icons.Material.Filled.People"
|
||||
Href="@($"/commission/payouts?week={context.Item.WeekNumber}")">
|
||||
پرداختها
|
||||
</MudButton>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
</MudDataGrid>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Class="mt-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">مجموع استخرها</MudText>
|
||||
<MudText Typo="Typo.h4">@_totalPoolSum.ToString("N0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ریال</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Success">محاسبه شده</MudText>
|
||||
<MudText Typo="Typo.h4">@_calculatedCount</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">هفته</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Warning">در انتظار</MudText>
|
||||
<MudText Typo="Typo.h4">@_pendingCount</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">هفته</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Info">میانگین ارزش</MudText>
|
||||
<MudText Typo="Typo.h4">@_averageValue.ToString("N0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ریال</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
[Inject] public CommissionContract.CommissionContractClient CommissionClient { get; set; }
|
||||
|
||||
private List<WeeklyPoolReportModel> _reports = new();
|
||||
private bool _loading = false;
|
||||
private string _filterFromWeek = "";
|
||||
private string _filterToWeek = "";
|
||||
|
||||
// Statistics
|
||||
private long _totalPoolSum = 0;
|
||||
private int _calculatedCount = 0;
|
||||
private int _pendingCount = 0;
|
||||
private long _averageValue = 0;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
var request = new GetAllWeeklyPoolsRequest
|
||||
{
|
||||
PageIndex = 1,
|
||||
PageSize = 100
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_filterFromWeek))
|
||||
{
|
||||
request.FromWeek = _filterFromWeek;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_filterToWeek))
|
||||
{
|
||||
request.ToWeek = _filterToWeek;
|
||||
}
|
||||
|
||||
var response = await CommissionClient.GetAllWeeklyPoolsAsync(request);
|
||||
|
||||
_reports = response.Models.Select(x => new WeeklyPoolReportModel
|
||||
{
|
||||
WeekNumber = x.WeekNumber,
|
||||
TotalPoolAmount = x.TotalPoolAmount,
|
||||
TotalBalances = x.TotalBalances,
|
||||
ValuePerBalance = x.ValuePerBalance,
|
||||
IsCalculated = x.IsCalculated,
|
||||
CalculatedAt = x.CalculatedAt
|
||||
}).ToList();
|
||||
|
||||
CalculateStatistics();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری دادهها: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyFilter()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private void ViewDetails(WeeklyPoolReportModel report)
|
||||
{
|
||||
Navigation.NavigateTo($"/commission/dashboard?week={report.WeekNumber}");
|
||||
}
|
||||
|
||||
private void CalculateStatistics()
|
||||
{
|
||||
if (_reports == null || !_reports.Any()) return;
|
||||
|
||||
_totalPoolSum = _reports.Sum(r => r.TotalPoolAmount);
|
||||
_calculatedCount = _reports.Count(r => r.IsCalculated);
|
||||
_pendingCount = _reports.Count(r => !r.IsCalculated);
|
||||
_averageValue = _reports.Any() ? (long)_reports.Average(r => r.ValuePerBalance) : 0;
|
||||
}
|
||||
|
||||
// Mock data generator - remove when BFF endpoint is ready
|
||||
private List<WeeklyPoolReportModel> GenerateMockData()
|
||||
{
|
||||
var reports = new List<WeeklyPoolReportModel>();
|
||||
var random = new Random();
|
||||
|
||||
for (int week = 40; week <= 48; week++)
|
||||
{
|
||||
reports.Add(new WeeklyPoolReportModel
|
||||
{
|
||||
WeekNumber = $"2025-W{week:D2}",
|
||||
TotalPoolAmount = random.Next(50000000, 200000000),
|
||||
TotalBalances = random.Next(100, 500),
|
||||
ValuePerBalance = random.Next(100000, 500000),
|
||||
IsCalculated = week < 48,
|
||||
CalculatedAt = week < 48 ? Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-(48 - week) * 7)) : null
|
||||
});
|
||||
}
|
||||
|
||||
return reports.OrderByDescending(r => r.WeekNumber).ToList();
|
||||
}
|
||||
|
||||
// Model for weekly pool reports
|
||||
private class WeeklyPoolReportModel
|
||||
{
|
||||
public string WeekNumber { get; set; }
|
||||
public long TotalPoolAmount { get; set; }
|
||||
public int TotalBalances { get; set; }
|
||||
public long ValuePerBalance { get; set; }
|
||||
public bool IsCalculated { get; set; }
|
||||
public Timestamp CalculatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
125
src/BackOffice/Pages/Commission/WithdrawalRequests.razor
Normal file
125
src/BackOffice/Pages/Commission/WithdrawalRequests.razor
Normal file
@@ -0,0 +1,125 @@
|
||||
@page "/commission/withdrawals"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.Commission.Protobuf
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">درخواستهای برداشت</MudText>
|
||||
|
||||
<MudDataGrid T="WithdrawalRequestModel"
|
||||
ServerData="@(new Func<GridState<WithdrawalRequestModel>, Task<GridData<WithdrawalRequestModel>>>(ServerReload))"
|
||||
Hover="true"
|
||||
@ref="_gridData"
|
||||
Height="75vh">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">درخواستهای برداشت</MudText>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudSelect @bind-Value="_filterStatus"
|
||||
Label="وضعیت"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Style="max-width: 150px;">
|
||||
<MudSelectItem Value="@((int?)null)">همه</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)0)">در انتظار</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)1)">تایید شده</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)2)">رد شده</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)3)">پردازش شده</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="ApplyFilter"
|
||||
StartIcon="@Icons.Material.Filled.Search">
|
||||
جستجو
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
|
||||
<PropertyColumn Property="x => x.UserId" Title="کاربر">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2"><strong>@context.Item.UserName</strong></MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ID: @context.Item.UserId</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
||||
<CellTemplate>
|
||||
<MudText Typo="Typo.body2" Color="Color.Primary">
|
||||
<strong>@context.Item.Amount.ToString("N0") ریال</strong>
|
||||
</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Method" Title="روش برداشت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||
@GetMethodText(context.Item.Method)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Status" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Color="@GetStatusColor(context.Item.Status)"
|
||||
Size="Size.Small">
|
||||
@GetStatusText(context.Item.Status)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.RequestedAt" Title="تاریخ درخواست">
|
||||
<CellTemplate>
|
||||
@context.Item.RequestedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<TemplateColumn Title="عملیات" Sortable="false">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" Spacing="1">
|
||||
<MudTooltip Text="مشاهده جزئیات">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Visibility"
|
||||
Size="Size.Small"
|
||||
Color="Color.Info"
|
||||
OnClick="@(() => ViewDetails(context.Item))" />
|
||||
</MudTooltip>
|
||||
@if (context.Item.Status == 0)
|
||||
{
|
||||
<MudTooltip Text="تایید">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Check"
|
||||
Size="Size.Small"
|
||||
Color="Color.Success"
|
||||
OnClick="@(() => ApproveRequest(context.Item))" />
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="رد">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Close"
|
||||
Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => RejectRequest(context.Item))" />
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (context.Item.Status == 1)
|
||||
{
|
||||
<MudTooltip Text="پردازش">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Payment"
|
||||
Size="Size.Small"
|
||||
Color="Color.Primary"
|
||||
OnClick="@(() => ProcessRequest(context.Item))" />
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="WithdrawalRequestModel"
|
||||
PageSizeOptions="@(new int[] { 10, 25, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد سطرهای صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudContainer>
|
||||
170
src/BackOffice/Pages/Commission/WithdrawalRequests.razor.cs
Normal file
170
src/BackOffice/Pages/Commission/WithdrawalRequests.razor.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using BackOffice.BFF.Commission.Protobuf;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Commission;
|
||||
|
||||
public partial class WithdrawalRequests
|
||||
{
|
||||
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
||||
|
||||
private MudDataGrid<WithdrawalRequestModel> _gridData;
|
||||
private int? _filterStatus;
|
||||
|
||||
private async Task<GridData<WithdrawalRequestModel>> ServerReload(GridState<WithdrawalRequestModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Implement GetWithdrawalRequestsRequest in CMS Protobuf
|
||||
/*
|
||||
var request = new GetWithdrawalRequestsRequest
|
||||
{
|
||||
PageIndex = state.Page + 1,
|
||||
PageSize = state.PageSize
|
||||
};
|
||||
|
||||
if (_filterStatus.HasValue)
|
||||
{
|
||||
request.Status = _filterStatus.Value;
|
||||
}
|
||||
|
||||
var result = await CommissionContract.GetWithdrawalRequestsAsync(request);
|
||||
*/
|
||||
|
||||
// Mock data until API is ready
|
||||
await Task.CompletedTask;
|
||||
var result = new { Models = new List<WithdrawalRequestModel>(), TotalCount = 0 };
|
||||
|
||||
if (result?.Models != null && result.Models.Any())
|
||||
{
|
||||
return new GridData<WithdrawalRequestModel>
|
||||
{
|
||||
Items = result.Models.ToList(),
|
||||
TotalItems = result.TotalCount
|
||||
};
|
||||
}
|
||||
|
||||
return new GridData<WithdrawalRequestModel>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری دادهها: {ex.Message}", Severity.Error);
|
||||
return new GridData<WithdrawalRequestModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyFilter()
|
||||
{
|
||||
if (_gridData != null)
|
||||
{
|
||||
await _gridData.ReloadServerData();
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetStatusColor(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => Color.Warning, // Pending
|
||||
1 => Color.Success, // Approved
|
||||
2 => Color.Error, // Rejected
|
||||
3 => Color.Info, // Processed
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private string GetStatusText(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => "در انتظار",
|
||||
1 => "تایید شده",
|
||||
2 => "رد شده",
|
||||
3 => "پردازش شده",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private string GetMethodText(string method)
|
||||
{
|
||||
return method switch
|
||||
{
|
||||
"Bank" => "انتقال بانکی",
|
||||
"Crypto" => "ارز دیجیتال",
|
||||
"Cash" => "نقدی",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private void ViewDetails(WithdrawalRequestModel request)
|
||||
{
|
||||
Snackbar.Add($"جزئیات درخواست {request.Id}", Severity.Info);
|
||||
// TODO: Open details dialog
|
||||
}
|
||||
|
||||
private async Task ApproveRequest(WithdrawalRequestModel request)
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"تایید درخواست",
|
||||
$"آیا از تایید درخواست برداشت {request.Amount:N0} ریال برای {request.UserName} مطمئن هستید؟",
|
||||
yesText: "تایید", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Call ApproveWithdrawal API
|
||||
Snackbar.Add("درخواست با موفقیت تایید شد", Severity.Success);
|
||||
await ApplyFilter();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در تایید: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RejectRequest(WithdrawalRequestModel request)
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"رد درخواست",
|
||||
$"آیا از رد درخواست برداشت {request.Amount:N0} ریال برای {request.UserName} مطمئن هستید؟",
|
||||
yesText: "رد", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Call RejectWithdrawal API
|
||||
Snackbar.Add("درخواست رد شد", Severity.Warning);
|
||||
await ApplyFilter();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در رد درخواست: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessRequest(WithdrawalRequestModel request)
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"پردازش پرداخت",
|
||||
$"آیا پرداخت {request.Amount:N0} ریال به {request.UserName} انجام شده است؟",
|
||||
yesText: "بله، پردازش شد", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Call ProcessWithdrawal API
|
||||
Snackbar.Add("درخواست با موفقیت پردازش شد", Severity.Success);
|
||||
await ApplyFilter();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در پردازش: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
248
src/BackOffice/Pages/Network/BalancesReport.razor
Normal file
248
src/BackOffice/Pages/Network/BalancesReport.razor
Normal file
@@ -0,0 +1,248 @@
|
||||
@page "/network/balances"
|
||||
|
||||
@using MudBlazor
|
||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">گزارش موجودیهای هفتگی</MudText>
|
||||
<MudText Typo="Typo.body1" Color="Color.Secondary" Class="mb-4">
|
||||
مشاهده موجودیهای شبکه کاربران به تفکیک هفته
|
||||
</MudText>
|
||||
|
||||
<MudCard Class="mb-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudNumericField @bind-Value="_filterUserId"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudTextField @bind-Value="_filterWeekNumber"
|
||||
Label="شماره هفته"
|
||||
Placeholder="2025-W48"
|
||||
Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudNumericField @bind-Value="_minBalance"
|
||||
Label="حداقل موجودی"
|
||||
Variant="Variant.Outlined"
|
||||
Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudNumericField @bind-Value="_maxBalance"
|
||||
Label="حداکثر موجودی"
|
||||
Variant="Variant.Outlined"
|
||||
Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2" Class="d-flex align-center">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="ApplyFilter"
|
||||
StartIcon="@Icons.Material.Filled.Search"
|
||||
FullWidth="true">
|
||||
جستجو
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center mb-3">
|
||||
<MudText Typo="Typo.h6">موجودیهای هفتگی</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Success"
|
||||
StartIcon="@Icons.Material.Filled.Download"
|
||||
OnClick="ExportToExcel">
|
||||
خروجی Excel
|
||||
</MudButton>
|
||||
</div>
|
||||
|
||||
<MudDataGrid T="UserWeeklyBalanceModel"
|
||||
ServerData="@(new Func<GridState<UserWeeklyBalanceModel>, Task<GridData<UserWeeklyBalanceModel>>>(ServerReload))"
|
||||
Filterable="true"
|
||||
Hover="true">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.UserId" Title="شناسه کاربر" />
|
||||
<PropertyColumn Property="x => x.UserName" Title="نام کاربر" />
|
||||
<PropertyColumn Property="x => x.WeekNumber" Title="هفته" />
|
||||
<PropertyColumn Property="x => x.LeftBalance" Title="موجودی چپ">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Success" Size="Size.Small">
|
||||
@context.Item.LeftBalance
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.RightBalance" Title="موجودی راست">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Warning" Size="Size.Small">
|
||||
@context.Item.RightBalance
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.MatchedBalance" Title="موجودی تطبیقیافته">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||
@context.Item.MatchedBalance
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.CarryOverLeft" Title="نقلشده چپ" />
|
||||
<PropertyColumn Property="x => x.CarryOverRight" Title="نقلشده راست" />
|
||||
<TemplateColumn Title="عملیات">
|
||||
<CellTemplate>
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Person"
|
||||
Href="@($"/network/user-info/{context.Item.UserId}")">
|
||||
مشاهده کاربر
|
||||
</MudButton>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="UserWeeklyBalanceModel"
|
||||
PageSizeOptions="new int[] { 10, 25, 50, 100 }" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Class="mt-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Success">مجموع موجودی چپ</MudText>
|
||||
<MudText Typo="Typo.h4">@_totalLeftBalance.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Warning">مجموع موجودی راست</MudText>
|
||||
<MudText Typo="Typo.h4">@_totalRightBalance.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Info">مجموع تطبیقیافته</MudText>
|
||||
<MudText Typo="Typo.h4">@_totalMatchedBalance.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkClient { get; set; }
|
||||
|
||||
private long? _filterUserId = null;
|
||||
private string _filterWeekNumber = "";
|
||||
private int? _minBalance = null;
|
||||
private int? _maxBalance = null;
|
||||
|
||||
private int _totalLeftBalance = 0;
|
||||
private int _totalRightBalance = 0;
|
||||
private int _totalMatchedBalance = 0;
|
||||
|
||||
private async Task<GridData<UserWeeklyBalanceModel>> ServerReload(GridState<UserWeeklyBalanceModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Implement GetUserWeeklyBalancesRequest in CMS Protobuf
|
||||
// Mock data until API is ready
|
||||
await Task.CompletedTask;
|
||||
|
||||
var items = new List<UserWeeklyBalanceModel>();
|
||||
|
||||
/*
|
||||
var request = new GetUserWeeklyBalancesRequest
|
||||
{
|
||||
PageNumber = state.Page + 1,
|
||||
PageSize = state.PageSize,
|
||||
WeekNumber = _filterWeekNumber ?? ""
|
||||
};
|
||||
|
||||
if (_filterUserId.HasValue && _filterUserId.Value > 0)
|
||||
{
|
||||
request.UserId = _filterUserId.Value;
|
||||
}
|
||||
|
||||
var response = await NetworkClient.GetUserWeeklyBalancesAsync(request);
|
||||
|
||||
items = response.Balances.Select(b => new UserWeeklyBalanceModel
|
||||
{
|
||||
UserId = b.UserId,
|
||||
UserName = b.UserName,
|
||||
WeekNumber = b.WeekNumber,
|
||||
LeftBalance = b.LeftBalance,
|
||||
RightBalance = b.RightBalance,
|
||||
MatchedBalance = b.MatchedBalance,
|
||||
CarryOverLeft = b.CarryOverLeft,
|
||||
CarryOverRight = b.CarryOverRight
|
||||
}).ToList();
|
||||
|
||||
// Apply balance range filter if specified
|
||||
if (_minBalance.HasValue || _maxBalance.HasValue)
|
||||
{
|
||||
items = items.Where(b =>
|
||||
(!_minBalance.HasValue || b.MatchedBalance >= _minBalance.Value) &&
|
||||
(!_maxBalance.HasValue || b.MatchedBalance <= _maxBalance.Value)
|
||||
).ToList();
|
||||
}
|
||||
*/
|
||||
|
||||
CalculateTotals(items);
|
||||
|
||||
return new GridData<UserWeeklyBalanceModel>
|
||||
{
|
||||
Items = items,
|
||||
TotalItems = 0
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری دادهها: {ex.Message}", Severity.Error);
|
||||
return new GridData<UserWeeklyBalanceModel> { Items = new List<UserWeeklyBalanceModel>(), TotalItems = 0 };
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyFilter()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void CalculateTotals(List<UserWeeklyBalanceModel> items)
|
||||
{
|
||||
_totalLeftBalance = items.Sum(b => b.LeftBalance);
|
||||
_totalRightBalance = items.Sum(b => b.RightBalance);
|
||||
_totalMatchedBalance = items.Sum(b => b.MatchedBalance);
|
||||
}
|
||||
|
||||
private async Task ExportToExcel()
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"خروجی Excel",
|
||||
"این ویژگی به زودی اضافه خواهد شد.",
|
||||
yesText: "باشه");
|
||||
|
||||
// TODO: Implement Excel export using EPPlus or ClosedXML
|
||||
}
|
||||
|
||||
private class UserWeeklyBalanceModel
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string WeekNumber { get; set; }
|
||||
public int LeftBalance { get; set; }
|
||||
public int RightBalance { get; set; }
|
||||
public int MatchedBalance { get; set; }
|
||||
public int CarryOverLeft { get; set; }
|
||||
public int CarryOverRight { get; set; }
|
||||
}
|
||||
}
|
||||
155
src/BackOffice/Pages/Network/NetworkTreeViewer.razor
Normal file
155
src/BackOffice/Pages/Network/NetworkTreeViewer.razor
Normal file
@@ -0,0 +1,155 @@
|
||||
@page "/network/tree"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">درخت شبکه</MudText>
|
||||
|
||||
<MudPaper Class="pa-4 mb-4">
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||
<MudNumericField @bind-Value="_searchUserId"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
Style="max-width: 200px;" />
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="LoadTree"
|
||||
StartIcon="@Icons.Material.Filled.Search"
|
||||
Disabled="_isLoading">
|
||||
نمایش درخت
|
||||
</MudButton>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||
کل اعضا: @_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>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudPaper Class="pa-8 d-flex justify-center">
|
||||
<MudProgressCircular Color="Color.Primary" Size="Size.Large" Indeterminate="true" />
|
||||
</MudPaper>
|
||||
}
|
||||
else if (_treeData != null && _treeData.Nodes.Any())
|
||||
{
|
||||
<MudPaper Class="pa-4">
|
||||
<MudDataGrid T="NetworkTreeNodeModel" Items="@_treeData.Nodes" Hover="true" Filterable="true">
|
||||
<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.IsActive" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@(context.Item.IsActive ? "فعال" : "غیرفعال")
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.JoinedAt" Title="تاریخ عضویت">
|
||||
<CellTemplate>
|
||||
@context.Item.JoinedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<TemplateColumn Title="عملیات" Sortable="false">
|
||||
<CellTemplate>
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
OnClick="@(() => ViewUserDetails(context.Item.UserId))">
|
||||
جزئیات
|
||||
</MudButton>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
</MudDataGrid>
|
||||
</MudPaper>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudPaper Class="pa-8">
|
||||
<MudAlert Severity="Severity.Info">
|
||||
برای نمایش درخت شبکه، شناسه کاربر را وارد کنید و دکمه "نمایش درخت" را بزنید.
|
||||
</MudAlert>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||
|
||||
private long _searchUserId;
|
||||
private GetNetworkTreeResponse _treeData;
|
||||
private bool _isLoading;
|
||||
private int _totalMembers;
|
||||
private int _leftCount;
|
||||
private int _rightCount;
|
||||
|
||||
private async Task LoadTree()
|
||||
{
|
||||
if (_searchUserId <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً شناسه کاربر را وارد کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
try
|
||||
{
|
||||
var request = new GetNetworkTreeRequest { RootUserId = _searchUserId };
|
||||
_treeData = await NetworkContract.GetNetworkTreeAsync(request);
|
||||
|
||||
CalculateStats();
|
||||
|
||||
Snackbar.Add($"درخت بارگذاری شد - {_treeData.Nodes.Count} عضو", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری درخت: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateStats()
|
||||
{
|
||||
if (_treeData == null || !_treeData.Nodes.Any()) return;
|
||||
|
||||
_totalMembers = _treeData.Nodes.Count;
|
||||
_leftCount = _treeData.Nodes.Count(n => n.NetworkLeg == 0);
|
||||
_rightCount = _treeData.Nodes.Count(n => n.NetworkLeg == 1);
|
||||
}
|
||||
|
||||
private void ViewUserDetails(long userId)
|
||||
{
|
||||
NavigationManager.NavigateTo($"/network/user-info/{userId}");
|
||||
}
|
||||
}
|
||||
292
src/BackOffice/Pages/Network/Statistics.razor
Normal file
292
src/BackOffice/Pages/Network/Statistics.razor
Normal file
@@ -0,0 +1,292 @@
|
||||
@page "/network/statistics"
|
||||
|
||||
@using MudBlazor
|
||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">آمار شبکه</MudText>
|
||||
<MudText Typo="Typo.body1" Color="Color.Secondary" Class="mb-4">
|
||||
نمای کلی از وضعیت شبکه و آمار اعضا
|
||||
</MudText>
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Summary Cards -->
|
||||
<MudGrid Class="mb-4">
|
||||
<MudItem xs="12" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">کل اعضا</MudText>
|
||||
<MudText Typo="Typo.h4">@_totalMembers.ToString("N0")</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.People" Color="Color.Primary" Size="Size.Large" />
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">شاخه چپ</MudText>
|
||||
<MudText Typo="Typo.h4">@_leftCount.ToString("N0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Success">@_leftPercentage%</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Success" Size="Size.Large" />
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">شاخه راست</MudText>
|
||||
<MudText Typo="Typo.h4">@_rightCount.ToString("N0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Warning">@_rightPercentage%</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.TrendingDown" Color="Color.Warning" Size="Size.Large" />
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">میانگین عمق</MudText>
|
||||
<MudText Typo="Typo.h4">@_averageDepth.ToString("F1")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Info">سطح</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Height" Color="Color.Info" Size="Size.Large" />
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Distribution Chart -->
|
||||
<MudGrid Class="mb-4">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">توزیع شاخهها</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudChart ChartType="ChartType.Donut"
|
||||
Width="300px"
|
||||
Height="300px"
|
||||
InputData="@_distributionData"
|
||||
InputLabels="@_distributionLabels">
|
||||
</MudChart>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">رشد ماهانه</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudChart ChartType="ChartType.Line"
|
||||
ChartSeries="@_growthSeries"
|
||||
XAxisLabels="@_growthLabels"
|
||||
Width="100%"
|
||||
Height="300px">
|
||||
</MudChart>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Depth Distribution -->
|
||||
<MudCard Class="mb-4">
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">توزیع عمق شبکه</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudChart ChartType="ChartType.Bar"
|
||||
ChartSeries="@_depthSeries"
|
||||
XAxisLabels="@_depthLabels"
|
||||
Width="100%"
|
||||
Height="300px">
|
||||
</MudChart>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<!-- Top 10 Users -->
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">10 کاربر برتر (بیشترین زیرمجموعه)</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudTable T="TopUserModel" Items="@_topUsers" Hover="true" Dense="true">
|
||||
<HeaderContent>
|
||||
<MudTh>رتبه</MudTh>
|
||||
<MudTh>شناسه کاربر</MudTh>
|
||||
<MudTh>نام کاربر</MudTh>
|
||||
<MudTh>تعداد زیرمجموعه</MudTh>
|
||||
<MudTh>شاخه چپ</MudTh>
|
||||
<MudTh>شاخه راست</MudTh>
|
||||
<MudTh>عملیات</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="رتبه">
|
||||
<MudChip T="string" Color="@GetRankColor(context.Rank)" Size="Size.Small">@context.Rank</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="شناسه کاربر">@context.UserId</MudTd>
|
||||
<MudTd DataLabel="نام کاربر">@context.UserName</MudTd>
|
||||
<MudTd DataLabel="تعداد زیرمجموعه">@context.TotalChildren.ToString("N0")</MudTd>
|
||||
<MudTd DataLabel="شاخه چپ">@context.LeftCount.ToString("N0")</MudTd>
|
||||
<MudTd DataLabel="شاخه راست">@context.RightCount.ToString("N0")</MudTd>
|
||||
<MudTd DataLabel="عملیات">
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Visibility"
|
||||
Href="@($"/network/user-info/{context.UserId}")">
|
||||
مشاهده
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkClient { get; set; }
|
||||
|
||||
private bool _loading = false;
|
||||
|
||||
// Statistics
|
||||
private int _totalMembers = 0;
|
||||
private int _leftCount = 0;
|
||||
private int _rightCount = 0;
|
||||
private int _leftPercentage = 0;
|
||||
private int _rightPercentage = 0;
|
||||
private double _averageDepth = 0;
|
||||
|
||||
// Chart data
|
||||
private double[] _distributionData = Array.Empty<double>();
|
||||
private string[] _distributionLabels = Array.Empty<string>();
|
||||
private List<ChartSeries> _growthSeries = new();
|
||||
private string[] _growthLabels = Array.Empty<string>();
|
||||
private List<ChartSeries> _depthSeries = new();
|
||||
private string[] _depthLabels = Array.Empty<string>();
|
||||
|
||||
private List<TopUserModel> _topUsers = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadStatistics();
|
||||
}
|
||||
|
||||
private async Task LoadStatistics()
|
||||
{
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
// TODO: Implement GetNetworkStatisticsQuery in CMS and BFF
|
||||
// For now, generate mock data
|
||||
GenerateMockStatistics();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری آمار: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateMockStatistics()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
// Basic stats
|
||||
_totalMembers = random.Next(1000, 5000);
|
||||
_leftCount = random.Next(400, 2500);
|
||||
_rightCount = _totalMembers - _leftCount;
|
||||
_leftPercentage = (int)((_leftCount / (double)_totalMembers) * 100);
|
||||
_rightPercentage = 100 - _leftPercentage;
|
||||
_averageDepth = random.Next(3, 8) + random.NextDouble();
|
||||
|
||||
// Distribution chart
|
||||
_distributionData = new double[] { _leftCount, _rightCount };
|
||||
_distributionLabels = new[] { "شاخه چپ", "شاخه راست" };
|
||||
|
||||
// Growth chart
|
||||
_growthLabels = new[] { "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند" };
|
||||
_growthSeries = new List<ChartSeries>
|
||||
{
|
||||
new ChartSeries
|
||||
{
|
||||
Name = "اعضای جدید",
|
||||
Data = Enumerable.Range(0, 6).Select(_ => (double)random.Next(50, 200)).ToArray()
|
||||
}
|
||||
};
|
||||
|
||||
// Depth distribution
|
||||
_depthLabels = new[] { "سطح 1", "سطح 2", "سطح 3", "سطح 4", "سطح 5", "سطح 6+" };
|
||||
_depthSeries = new List<ChartSeries>
|
||||
{
|
||||
new ChartSeries
|
||||
{
|
||||
Name = "تعداد اعضا",
|
||||
Data = new double[]
|
||||
{
|
||||
random.Next(100, 200),
|
||||
random.Next(200, 400),
|
||||
random.Next(300, 600),
|
||||
random.Next(200, 400),
|
||||
random.Next(100, 200),
|
||||
random.Next(50, 100)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Top users
|
||||
_topUsers = Enumerable.Range(1, 10).Select(i => new TopUserModel
|
||||
{
|
||||
Rank = i,
|
||||
UserId = random.Next(1000, 9999),
|
||||
UserName = $"کاربر {i}",
|
||||
TotalChildren = random.Next(50, 500),
|
||||
LeftCount = random.Next(20, 250),
|
||||
RightCount = random.Next(20, 250)
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private Color GetRankColor(int rank)
|
||||
{
|
||||
return rank switch
|
||||
{
|
||||
1 => Color.Warning,
|
||||
2 => Color.Default,
|
||||
3 => Color.Tertiary,
|
||||
_ => Color.Primary
|
||||
};
|
||||
}
|
||||
|
||||
private class TopUserModel
|
||||
{
|
||||
public int Rank { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public int TotalChildren { get; set; }
|
||||
public int LeftCount { get; set; }
|
||||
public int RightCount { get; set; }
|
||||
}
|
||||
}
|
||||
225
src/BackOffice/Pages/Network/UserNetworkInfo.razor
Normal file
225
src/BackOffice/Pages/Network/UserNetworkInfo.razor
Normal file
@@ -0,0 +1,225 @@
|
||||
@page "/network/user-info/{UserId:long}"
|
||||
@attribute [Authorize]
|
||||
|
||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
|
||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-4"></MudBreadcrumbs>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudPaper Class="pa-8 d-flex justify-center">
|
||||
<MudProgressCircular Color="Color.Primary" Size="Size.Large" Indeterminate="true" />
|
||||
</MudPaper>
|
||||
}
|
||||
else if (_userInfo != null)
|
||||
{
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">اطلاعات کاربر</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه:</strong></td>
|
||||
<td>@_userInfo.UserId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>نام کاربر:</strong></td>
|
||||
<td>@_userInfo.UserName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>موقعیت:</strong></td>
|
||||
<td>
|
||||
<MudChip T="string"
|
||||
Color="@(_userInfo.NetworkLeg == 0 ? Color.Success : Color.Warning)"
|
||||
Size="Size.Small">
|
||||
@(_userInfo.NetworkLeg == 0 ? "چپ" : "راست")
|
||||
</MudChip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>عمق در درخت:</strong></td>
|
||||
<td>@_userInfo.NetworkLevel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تاریخ عضویت:</strong></td>
|
||||
<td>@_userInfo.JoinedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">ساختار شبکه</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
@if (_userInfo.ParentId.HasValue && _userInfo.ParentId.Value > 0)
|
||||
{
|
||||
<tr>
|
||||
<td><strong>والد:</strong></td>
|
||||
<td>
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.ParentId))">
|
||||
@_userInfo.ParentName (ID: @_userInfo.ParentId)
|
||||
</MudButton>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td><strong>زیرمجموعه چپ:</strong></td>
|
||||
<td>
|
||||
@if (_userInfo.LeftChildId > 0)
|
||||
{
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Success"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.LeftChildId))">
|
||||
@_userInfo.LeftChildName (ID: @_userInfo.LeftChildId)
|
||||
</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Color="Color.Secondary">خالی</MudText>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>زیرمجموعه راست:</strong></td>
|
||||
<td>
|
||||
@if (_userInfo.RightChildId > 0)
|
||||
{
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Warning"
|
||||
OnClick="@(() => NavigateToUser(_userInfo.RightChildId))">
|
||||
@_userInfo.RightChildName (ID: @_userInfo.RightChildId)
|
||||
</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Color="Color.Secondary">خالی</MudText>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">آمار شبکه</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudAlert Severity="Severity.Info">
|
||||
<MudText>آمار تجمعی شبکه در نسخه بعدی اضافه خواهد شد</MudText>
|
||||
</MudAlert>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">عملیات</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.AccountTree"
|
||||
OnClick="@(() => NavigationManager.NavigateTo($"/network/tree?userId={UserId}"))">
|
||||
نمایش درخت کامل
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Info"
|
||||
StartIcon="@Icons.Material.Filled.History"
|
||||
OnClick="ViewHistory">
|
||||
تاریخچه تغییرات
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAlert Severity="Severity.Error">
|
||||
کاربر یافت نشد یا خطایی رخ داده است.
|
||||
</MudAlert>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
[Parameter] public long UserId { get; set; }
|
||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||
|
||||
private GetUserNetworkResponse _userInfo;
|
||||
private bool _isLoading = true;
|
||||
private List<BreadcrumbItem> _breadcrumbs = new();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await LoadUserInfo();
|
||||
}
|
||||
|
||||
private async Task LoadUserInfo()
|
||||
{
|
||||
_isLoading = true;
|
||||
try
|
||||
{
|
||||
var request = new GetUserNetworkRequest { UserId = UserId };
|
||||
_userInfo = await NetworkContract.GetUserNetworkAsync(request);
|
||||
|
||||
_breadcrumbs = new List<BreadcrumbItem>
|
||||
{
|
||||
new BreadcrumbItem("شبکه", href: "/network/tree"),
|
||||
new BreadcrumbItem($"کاربر: {_userInfo.UserName}", href: null, disabled: true)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری اطلاعات: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigateToUser(long? parentIdValue)
|
||||
{
|
||||
if (parentIdValue.HasValue)
|
||||
NavigationManager.NavigateTo($"/network/user-info/{parentIdValue.Value}");
|
||||
}
|
||||
|
||||
private async Task ViewHistory()
|
||||
{
|
||||
// TODO: Implement history view
|
||||
Snackbar.Add("این قابلیت به زودی اضافه خواهد شد", Severity.Info);
|
||||
}
|
||||
}
|
||||
358
src/BackOffice/Pages/SystemManagement/WorkerControl.razor
Normal file
358
src/BackOffice/Pages/SystemManagement/WorkerControl.razor
Normal file
@@ -0,0 +1,358 @@
|
||||
@page "/system/worker-control"
|
||||
|
||||
@using MudBlazor
|
||||
@using BackOffice.Pages.SystemManagement
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">کنترل Worker محاسبات</MudText>
|
||||
<MudText Typo="Typo.body1" Color="Color.Secondary" Class="mb-4">
|
||||
مدیریت و نظارت بر Worker محاسبات هفتگی کمیسیون
|
||||
</MudText>
|
||||
|
||||
<MudGrid>
|
||||
<!-- Worker Status -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">وضعیت Worker</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>وضعیت:</strong></td>
|
||||
<td>
|
||||
<MudChip T="string"
|
||||
Color="@(_workerStatus == WorkerStatus.Running ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@GetWorkerStatusText()
|
||||
</MudChip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>آخرین اجرا:</strong></td>
|
||||
<td>@_lastRunTime.ToString("yyyy/MM/dd HH:mm:ss")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>اجرای بعدی:</strong></td>
|
||||
<td>@_nextRunTime.ToString("yyyy/MM/dd HH:mm:ss")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تعداد اجراهای موفق:</strong></td>
|
||||
<td><MudChip T="string" Color="Color.Success" Size="Size.Small">@_successfulRuns</MudChip></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تعداد خطاها:</strong></td>
|
||||
<td><MudChip T="string" Color="Color.Error" Size="Size.Small">@_failedRuns</MudChip></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<!-- Control Panel -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">پنل کنترل</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_manualWeekNumber"
|
||||
Label="شماره هفته برای اجرای دستی"
|
||||
Placeholder="2025-W48"
|
||||
Variant="Variant.Outlined" />
|
||||
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.PlayArrow"
|
||||
OnClick="RunManualCalculation"
|
||||
Disabled="@_isProcessing">
|
||||
اجرای دستی محاسبات
|
||||
</MudButton>
|
||||
|
||||
<MudDivider />
|
||||
|
||||
@if (_workerStatus == WorkerStatus.Running)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Warning"
|
||||
FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.Pause"
|
||||
OnClick="PauseWorker"
|
||||
Disabled="@_isProcessing">
|
||||
توقف موقت Worker
|
||||
</MudButton>
|
||||
}
|
||||
else if (_workerStatus == WorkerStatus.Paused)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Success"
|
||||
FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.PlayArrow"
|
||||
OnClick="ResumeWorker"
|
||||
Disabled="@_isProcessing">
|
||||
ازسرگیری Worker
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Info"
|
||||
FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.Refresh"
|
||||
OnClick="RestartWorker"
|
||||
Disabled="@_isProcessing">
|
||||
راهاندازی مجدد Worker
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<!-- Execution Log -->
|
||||
<MudItem xs="12">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardHeader>
|
||||
<div class="d-flex justify-space-between align-center w-100">
|
||||
<MudText Typo="Typo.h6">تاریخچه اجرا</MudText>
|
||||
<MudButton Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Refresh"
|
||||
OnClick="RefreshLog">
|
||||
بروزرسانی
|
||||
</MudButton>
|
||||
</div>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
@if (_executionLog == null || !_executionLog.Any())
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">هیچ رکوردی یافت نشد</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable T="ExecutionLogModel" Items="@_executionLog" Dense="true" Hover="true" FixedHeader="true" Height="400px">
|
||||
<HeaderContent>
|
||||
<MudTh>زمان</MudTh>
|
||||
<MudTh>هفته</MudTh>
|
||||
<MudTh>وضعیت</MudTh>
|
||||
<MudTh>مدت زمان</MudTh>
|
||||
<MudTh>پیام</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="زمان">@context.ExecutedAt.ToString("yyyy/MM/dd HH:mm:ss")</MudTd>
|
||||
<MudTd DataLabel="هفته">@context.WeekNumber</MudTd>
|
||||
<MudTd DataLabel="وضعیت">
|
||||
<MudChip T="string"
|
||||
Color="@(context.IsSuccess ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@(context.IsSuccess ? "موفق" : "خطا")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="مدت زمان">@context.DurationSeconds ثانیه</MudTd>
|
||||
<MudTd DataLabel="پیام">
|
||||
<MudText Typo="Typo.body2" Color="@(context.IsSuccess ? Color.Default : Color.Error)">
|
||||
@context.Message
|
||||
</MudText>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@if (_isProcessing)
|
||||
{
|
||||
<MudOverlay Visible="true" DarkBackground="true">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||||
</MudOverlay>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
private WorkerStatus _workerStatus = WorkerStatus.Running;
|
||||
private DateTime _lastRunTime = DateTime.Now.AddHours(-2);
|
||||
private DateTime _nextRunTime = DateTime.Now.AddDays(1);
|
||||
private int _successfulRuns = 45;
|
||||
private int _failedRuns = 2;
|
||||
private string _manualWeekNumber = "";
|
||||
private bool _isProcessing = false;
|
||||
private List<ExecutionLogModel> _executionLog = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// TODO: Load actual worker status from API
|
||||
GenerateMockLog();
|
||||
}
|
||||
|
||||
private async Task RunManualCalculation()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_manualWeekNumber))
|
||||
{
|
||||
Snackbar.Add("لطفا شماره هفته را وارد کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"اجرای دستی محاسبات",
|
||||
$"آیا از اجرای محاسبات برای هفته {_manualWeekNumber} اطمینان دارید؟",
|
||||
yesText: "بله، اجرا شود", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
_isProcessing = true;
|
||||
try
|
||||
{
|
||||
// TODO: Call TriggerWeeklyCalculationCommand API
|
||||
await Task.Delay(2000); // Simulate API call
|
||||
|
||||
Snackbar.Add($"محاسبات هفته {_manualWeekNumber} با موفقیت آغاز شد", Severity.Success);
|
||||
_manualWeekNumber = "";
|
||||
await RefreshLog();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در اجرای محاسبات: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PauseWorker()
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"توقف Worker",
|
||||
"آیا از توقف موقت Worker اطمینان دارید؟ محاسبات خودکار متوقف خواهد شد.",
|
||||
yesText: "بله، متوقف شود", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
_isProcessing = true;
|
||||
try
|
||||
{
|
||||
// TODO: Call PauseWorker API
|
||||
await Task.Delay(1000);
|
||||
_workerStatus = WorkerStatus.Paused;
|
||||
Snackbar.Add("Worker با موفقیت متوقف شد", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResumeWorker()
|
||||
{
|
||||
_isProcessing = true;
|
||||
try
|
||||
{
|
||||
// TODO: Call ResumeWorker API
|
||||
await Task.Delay(1000);
|
||||
_workerStatus = WorkerStatus.Running;
|
||||
Snackbar.Add("Worker با موفقیت ازسرگیری شد", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RestartWorker()
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"راهاندازی مجدد Worker",
|
||||
"آیا از راهاندازی مجدد Worker اطمینان دارید؟",
|
||||
yesText: "بله", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
_isProcessing = true;
|
||||
try
|
||||
{
|
||||
// TODO: Call RestartWorker API
|
||||
await Task.Delay(1500);
|
||||
_workerStatus = WorkerStatus.Running;
|
||||
Snackbar.Add("Worker با موفقیت راهاندازی مجدد شد", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshLog()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Load actual execution log from API
|
||||
GenerateMockLog();
|
||||
Snackbar.Add("تاریخچه بروزرسانی شد", Severity.Info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری تاریخچه: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetWorkerStatusText()
|
||||
{
|
||||
return _workerStatus switch
|
||||
{
|
||||
WorkerStatus.Running => "در حال اجرا",
|
||||
WorkerStatus.Paused => "متوقف",
|
||||
WorkerStatus.Stopped => "خاموش",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private void GenerateMockLog()
|
||||
{
|
||||
var random = new Random();
|
||||
_executionLog = Enumerable.Range(0, 20).Select(i => new ExecutionLogModel
|
||||
{
|
||||
ExecutedAt = DateTime.Now.AddHours(-i * 168), // Weekly intervals
|
||||
WeekNumber = $"2025-W{48 - i:D2}",
|
||||
IsSuccess = random.Next(0, 10) < 9, // 90% success rate
|
||||
DurationSeconds = random.Next(30, 300),
|
||||
Message = random.Next(0, 10) < 9 ? "محاسبات با موفقیت انجام شد" : "خطا در اتصال به سرویس CMS"
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private enum WorkerStatus
|
||||
{
|
||||
Running,
|
||||
Paused,
|
||||
Stopped
|
||||
}
|
||||
|
||||
private class ExecutionLogModel
|
||||
{
|
||||
public DateTime ExecutedAt { get; set; }
|
||||
public string WeekNumber { get; set; }
|
||||
public bool IsSuccess { get; set; }
|
||||
public int DurationSeconds { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user