feat: Implement Commission and Network Statistics Pages with DTOs and Services
- Added CommissionDashboardPage and CommissionHistoryPage for displaying commission payouts and history. - Implemented WeeklyBalancePage to show weekly balance details. - Created NetworkStatisticsPage to display network statistics and tree structure. - Developed corresponding services (CommissionService, NetworkMembershipService) for data retrieval. - Introduced DTOs for Commission and Network data structures (CommissionPayoutDto, WeeklyBalanceDto, NetworkStatisticsDto). - Added mock data generation for testing purposes in services. - Enhanced UI with MudBlazor components for better user experience.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// DTO for Club Membership information
|
||||
/// </summary>
|
||||
public class ClubMembershipDto
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public int? DaysRemaining { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for Club Membership activation response
|
||||
/// </summary>
|
||||
public class ClubActivationResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public DateTime? ActivationDate { get; set; }
|
||||
public long AmountPaid { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Service for Club Membership operations
|
||||
/// TODO: Connect to FrontOffice.BFF gRPC ClubMembershipCQ
|
||||
/// </summary>
|
||||
public class ClubMembershipService
|
||||
{
|
||||
// TODO: Inject gRPC client when FrontOffice connects to BFF
|
||||
// private readonly ClubMembershipContract.ClubMembershipContractClient _client;
|
||||
|
||||
public ClubMembershipService()
|
||||
{
|
||||
// TODO: Initialize gRPC client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current user's club membership status
|
||||
/// Maps to: ClubMembershipCQ.GetMyClubMembership
|
||||
/// </summary>
|
||||
public async Task<ClubMembershipDto> GetMyMembershipAsync()
|
||||
{
|
||||
// TODO: Replace with actual gRPC call to BFF
|
||||
// var request = new GetMyClubMembershipRequest();
|
||||
// var response = await _client.GetMyClubMembershipAsync(request);
|
||||
// return MapToDto(response);
|
||||
|
||||
await Task.Delay(500); // Simulate network delay
|
||||
|
||||
// Mock data for now
|
||||
return new ClubMembershipDto
|
||||
{
|
||||
UserId = 1,
|
||||
IsActive = false,
|
||||
Status = "Inactive",
|
||||
DaysRemaining = null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate or renew club membership
|
||||
/// Maps to: ClubMembershipCQ.ActivateMyClubMembership
|
||||
/// </summary>
|
||||
public async Task<ClubActivationResponseDto> ActivateMembershipAsync(
|
||||
long packageId,
|
||||
string? activationCode,
|
||||
int durationMonths)
|
||||
{
|
||||
// TODO: Replace with actual gRPC call to BFF
|
||||
// var request = new ActivateMyClubMembershipRequest
|
||||
// {
|
||||
// PackageId = packageId,
|
||||
// ActivationCode = activationCode ?? string.Empty,
|
||||
// DurationMonths = durationMonths
|
||||
// };
|
||||
// var response = await _client.ActivateMyClubMembershipAsync(request);
|
||||
// return MapToDto(response);
|
||||
|
||||
await Task.Delay(1000); // Simulate network delay
|
||||
|
||||
// Mock successful activation
|
||||
return new ClubActivationResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "عضویت با موفقیت فعال شد",
|
||||
ActivationDate = DateTime.Now,
|
||||
AmountPaid = 56_000_000 * durationMonths
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// DTO for Commission Payout
|
||||
/// </summary>
|
||||
public class CommissionPayoutDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public int WeekNumber { get; set; }
|
||||
public string WeekLabel { get; set; } = string.Empty;
|
||||
public int BalancesEarned { get; set; }
|
||||
public long TotalAmount { get; set; }
|
||||
public string AmountFormatted { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public string StatusBadgeColor { get; set; } = string.Empty;
|
||||
public string DatePersian { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for paginated commission payouts
|
||||
/// </summary>
|
||||
public class CommissionPayoutsResponseDto
|
||||
{
|
||||
public List<CommissionPayoutDto> Payouts { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
public int PageNumber { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for Weekly Balance details
|
||||
/// </summary>
|
||||
public class WeeklyBalanceDto
|
||||
{
|
||||
public int WeekNumber { get; set; }
|
||||
public string WeekLabel { get; set; } = string.Empty;
|
||||
public long LeftBalance { get; set; }
|
||||
public long RightBalance { get; set; }
|
||||
public long MinBalance { get; set; }
|
||||
public int BalanceCount { get; set; }
|
||||
public long CalculatedCommission { get; set; }
|
||||
public long LeftCarryover { get; set; }
|
||||
public long RightCarryover { get; set; }
|
||||
public DateTime StartDate { get; set; }
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
// Formatted properties
|
||||
public string LeftBalanceFormatted => $"{LeftBalance:N0} تومان";
|
||||
public string RightBalanceFormatted => $"{RightBalance:N0} تومان";
|
||||
public string MinBalanceFormatted => $"{MinBalance:N0} تومان";
|
||||
public string CalculatedCommissionFormatted => $"{CalculatedCommission:N0} تومان";
|
||||
public string LeftCarryoverFormatted => $"{LeftCarryover:N0} تومان";
|
||||
public string RightCarryoverFormatted => $"{RightCarryover:N0} تومان";
|
||||
public string StartDatePersian => StartDate.ToString("yyyy/MM/dd");
|
||||
public string EndDatePersian => EndDate.ToString("yyyy/MM/dd");
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Service for Commission operations
|
||||
/// TODO: Connect to FrontOffice.BFF gRPC CommissionCQ
|
||||
/// </summary>
|
||||
public class CommissionService
|
||||
{
|
||||
// TODO: Inject gRPC client when FrontOffice connects to BFF
|
||||
// private readonly CommissionContract.CommissionContractClient _client;
|
||||
|
||||
public CommissionService()
|
||||
{
|
||||
// TODO: Initialize gRPC client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get commission payouts with pagination and filters
|
||||
/// Maps to: CommissionCQ.GetMyCommissionPayouts
|
||||
/// </summary>
|
||||
public async Task<CommissionPayoutsResponseDto> GetMyCommissionPayoutsAsync(
|
||||
int? weekNumber,
|
||||
string? status,
|
||||
int pageNumber,
|
||||
int pageSize)
|
||||
{
|
||||
// TODO: Replace with actual gRPC call to BFF
|
||||
// var request = new GetMyCommissionPayoutsRequest
|
||||
// {
|
||||
// WeekNumber = weekNumber,
|
||||
// Status = status ?? string.Empty,
|
||||
// PageNumber = pageNumber,
|
||||
// PageSize = pageSize
|
||||
// };
|
||||
// var response = await _client.GetMyCommissionPayoutsAsync(request);
|
||||
// return MapToDto(response);
|
||||
|
||||
await Task.Delay(500); // Simulate network delay
|
||||
|
||||
// Mock data
|
||||
var allPayouts = GenerateMockPayouts(50);
|
||||
|
||||
// Apply filters
|
||||
var filtered = allPayouts.AsEnumerable();
|
||||
if (weekNumber.HasValue)
|
||||
filtered = filtered.Where(p => p.WeekNumber == weekNumber.Value);
|
||||
if (!string.IsNullOrEmpty(status))
|
||||
filtered = filtered.Where(p => p.Status == status);
|
||||
|
||||
var total = filtered.Count();
|
||||
var paged = filtered.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return new CommissionPayoutsResponseDto
|
||||
{
|
||||
Payouts = paged,
|
||||
TotalCount = total,
|
||||
PageNumber = pageNumber,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get weekly balance details for a specific week
|
||||
/// Maps to: CommissionCQ.GetMyWeeklyBalances
|
||||
/// </summary>
|
||||
public async Task<WeeklyBalanceDto> GetMyWeeklyBalanceAsync(int? weekNumber = null)
|
||||
{
|
||||
// TODO: Replace with actual gRPC call to BFF
|
||||
// var request = new GetMyWeeklyBalancesRequest
|
||||
// {
|
||||
// WeekNumber = weekNumber
|
||||
// };
|
||||
// var response = await _client.GetMyWeeklyBalancesAsync(request);
|
||||
// return MapToDto(response);
|
||||
|
||||
await Task.Delay(500); // Simulate network delay
|
||||
|
||||
// Mock current week data
|
||||
var currentWeek = weekNumber ?? 45;
|
||||
return new WeeklyBalanceDto
|
||||
{
|
||||
WeekNumber = currentWeek,
|
||||
WeekLabel = $"هفته {currentWeek} - سال 1404",
|
||||
LeftBalance = 15_000_000,
|
||||
RightBalance = 12_000_000,
|
||||
MinBalance = 12_000_000,
|
||||
BalanceCount = 12,
|
||||
CalculatedCommission = 1_200_000,
|
||||
LeftCarryover = 3_000_000,
|
||||
RightCarryover = 0,
|
||||
StartDate = DateTime.Now.AddDays(-7),
|
||||
EndDate = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
#region Mock Data Helpers
|
||||
|
||||
private static List<CommissionPayoutDto> GenerateMockPayouts(int count)
|
||||
{
|
||||
var payouts = new List<CommissionPayoutDto>();
|
||||
var random = new Random();
|
||||
var statuses = new[] { "Created", "Paid", "WithdrawalRequested", "Withdrawn", "Cancelled" };
|
||||
var statusColors = new[] { "Info", "Success", "Warning", "Success", "Error" };
|
||||
var statusTexts = new[] { "ایجاد شده", "پرداخت شده", "درخواست برداشت", "برداشت شده", "لغو شده" };
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var statusIndex = random.Next(statuses.Length);
|
||||
var weekNumber = 50 - i;
|
||||
var balances = random.Next(5, 20);
|
||||
var amount = balances * 100_000;
|
||||
|
||||
payouts.Add(new CommissionPayoutDto
|
||||
{
|
||||
Id = i + 1,
|
||||
WeekNumber = weekNumber,
|
||||
WeekLabel = $"هفته {weekNumber} - سال 1404",
|
||||
BalancesEarned = balances,
|
||||
TotalAmount = amount,
|
||||
AmountFormatted = $"{amount:N0} تومان",
|
||||
Status = statusTexts[statusIndex],
|
||||
StatusBadgeColor = statusColors[statusIndex],
|
||||
DatePersian = DateTime.Now.AddDays(-i * 7).ToString("yyyy/MM/dd")
|
||||
});
|
||||
}
|
||||
|
||||
return payouts;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// DTO for Network Tree node
|
||||
/// </summary>
|
||||
public class NetworkNodeDto
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public string FullName { get; set; } = string.Empty;
|
||||
public string Mobile { get; set; } = string.Empty;
|
||||
public string? Avatar { get; set; }
|
||||
public string Position { get; set; } = string.Empty; // "Left" or "Right"
|
||||
public NetworkNodeDto? LeftChild { get; set; }
|
||||
public NetworkNodeDto? RightChild { get; set; }
|
||||
public int Level { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for Network Tree response
|
||||
/// </summary>
|
||||
public class NetworkTreeDto
|
||||
{
|
||||
public NetworkNodeDto? RootNode { get; set; }
|
||||
public int TotalMembers { get; set; }
|
||||
public int CurrentDepth { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for Last Member info
|
||||
/// </summary>
|
||||
public class LastMemberDto
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public string FullName { get; set; } = string.Empty;
|
||||
public string Position { get; set; } = string.Empty;
|
||||
public DateTime JoinedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for Network Statistics
|
||||
/// </summary>
|
||||
public class NetworkStatisticsDto
|
||||
{
|
||||
public int LeftLegCount { get; set; }
|
||||
public int RightLegCount { get; set; }
|
||||
public int TotalMembers { get; set; }
|
||||
public int TreeDepth { get; set; }
|
||||
public string WeakerLeg { get; set; } = string.Empty; // "Left" or "Right"
|
||||
public LastMemberDto? LastMember { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Service for Network Membership operations
|
||||
/// TODO: Connect to FrontOffice.BFF gRPC NetworkMembershipCQ
|
||||
/// </summary>
|
||||
public class NetworkMembershipService
|
||||
{
|
||||
// TODO: Inject gRPC client when FrontOffice connects to BFF
|
||||
// private readonly NetworkMembershipContract.NetworkMembershipContractClient _client;
|
||||
|
||||
public NetworkMembershipService()
|
||||
{
|
||||
// TODO: Initialize gRPC client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current user's network tree
|
||||
/// Maps to: NetworkMembershipCQ.GetMyNetworkTree
|
||||
/// </summary>
|
||||
public async Task<NetworkTreeDto> GetMyNetworkTreeAsync(int maxDepth = 3)
|
||||
{
|
||||
// TODO: Replace with actual gRPC call to BFF
|
||||
// var request = new GetMyNetworkTreeRequest { MaxDepth = maxDepth };
|
||||
// var response = await _client.GetMyNetworkTreeAsync(request);
|
||||
// return MapToDto(response);
|
||||
|
||||
await Task.Delay(500); // Simulate network delay
|
||||
|
||||
// Mock data with sample tree
|
||||
return new NetworkTreeDto
|
||||
{
|
||||
CurrentDepth = 2,
|
||||
TotalMembers = 5,
|
||||
RootNode = new NetworkNodeDto
|
||||
{
|
||||
UserId = 1,
|
||||
FullName = "شما",
|
||||
Mobile = "09121234567",
|
||||
Position = "Root",
|
||||
Level = 0,
|
||||
LeftChild = new NetworkNodeDto
|
||||
{
|
||||
UserId = 2,
|
||||
FullName = "علی محمدی",
|
||||
Mobile = "09121234568",
|
||||
Position = "Left",
|
||||
Level = 1,
|
||||
LeftChild = new NetworkNodeDto
|
||||
{
|
||||
UserId = 4,
|
||||
FullName = "رضا احمدی",
|
||||
Mobile = "09121234570",
|
||||
Position = "Left",
|
||||
Level = 2
|
||||
}
|
||||
},
|
||||
RightChild = new NetworkNodeDto
|
||||
{
|
||||
UserId = 3,
|
||||
FullName = "فاطمه حسینی",
|
||||
Mobile = "09121234569",
|
||||
Position = "Right",
|
||||
Level = 1,
|
||||
RightChild = new NetworkNodeDto
|
||||
{
|
||||
UserId = 5,
|
||||
FullName = "زهرا کریمی",
|
||||
Mobile = "09121234571",
|
||||
Position = "Right",
|
||||
Level = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current user's network statistics
|
||||
/// Maps to: NetworkMembershipCQ.GetMyNetworkStatistics
|
||||
/// </summary>
|
||||
public async Task<NetworkStatisticsDto> GetMyNetworkStatisticsAsync()
|
||||
{
|
||||
// TODO: Replace with actual gRPC call to BFF
|
||||
// var request = new GetMyNetworkStatisticsRequest();
|
||||
// var response = await _client.GetMyNetworkStatisticsAsync(request);
|
||||
// return MapToDto(response);
|
||||
|
||||
await Task.Delay(500); // Simulate network delay
|
||||
|
||||
// Mock statistics
|
||||
return new NetworkStatisticsDto
|
||||
{
|
||||
LeftLegCount = 15,
|
||||
RightLegCount = 12,
|
||||
TotalMembers = 27,
|
||||
TreeDepth = 4,
|
||||
WeakerLeg = "Right",
|
||||
LastMember = new LastMemberDto
|
||||
{
|
||||
UserId = 28,
|
||||
FullName = "محمد رضایی",
|
||||
Position = "Left",
|
||||
JoinedAt = DateTime.Now.AddHours(-2)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,25 @@ public static class RouteConstants
|
||||
public const string Wallet = "/profile/wallet";
|
||||
}
|
||||
|
||||
public static class Club
|
||||
{
|
||||
public const string Membership = "/club/membership";
|
||||
public const string Features = "/club/features";
|
||||
}
|
||||
|
||||
public static class Network
|
||||
{
|
||||
public const string Tree = "/network/tree";
|
||||
public const string Statistics = "/network/statistics";
|
||||
}
|
||||
|
||||
public static class Commission
|
||||
{
|
||||
public const string Dashboard = "/commission/dashboard";
|
||||
public const string History = "/commission/history";
|
||||
public const string WeeklyBalance = "/commission/weekly-balance";
|
||||
}
|
||||
|
||||
public static class Package
|
||||
{
|
||||
public const string Detail = "/package/";
|
||||
|
||||
@@ -28,7 +28,8 @@ public class WalletService
|
||||
try
|
||||
{
|
||||
var response = await _client.GetUserWalletAsync(new Empty());
|
||||
return new WalletBalances(response.Balance, response.DiscountBalance, response.NetworkBalance);
|
||||
// TODO: DiscountBalance will be added in BFF protobuf later
|
||||
return new WalletBalances(response.Balance, 0 /* response.DiscountBalance */, response.NetworkBalance);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -39,6 +40,9 @@ public class WalletService
|
||||
|
||||
public async Task<List<WalletTransaction>> GetTransactionsAsync(long? referenceId = null, bool? isIncrease = null)
|
||||
{
|
||||
// TODO: Implement when BFF protobuf has GetAllUserWalletChangeLog
|
||||
await Task.CompletedTask;
|
||||
/*
|
||||
try
|
||||
{
|
||||
var request = new GetAllUserWalletChangeLogRequest();
|
||||
@@ -62,6 +66,7 @@ public class WalletService
|
||||
}
|
||||
catch
|
||||
{
|
||||
*/
|
||||
// Fallback to mock data if backend is unavailable
|
||||
var _transactions = new List<WalletTransaction>
|
||||
{
|
||||
@@ -71,7 +76,7 @@ public class WalletService
|
||||
new(DateTime.Now.AddDays(-9).ToString(), 900_000, "کیف پول شرکای تجاری", "اعتبار خرید"),
|
||||
};
|
||||
return _transactions.OrderByDescending(t => t.Date).ToList();
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
private static string ResolveTransactionDate(GetAllUserWalletChangeLogResponseModel model)
|
||||
@@ -100,6 +105,10 @@ public class WalletService
|
||||
|
||||
public async Task<bool> RequestWithdrawalAsync(long payoutId, WithdrawalMethodClient method, string? iban)
|
||||
{
|
||||
// TODO: Implement when BFF protobuf has WithdrawBalance
|
||||
await Task.CompletedTask;
|
||||
return true;
|
||||
/*
|
||||
var request = new WithdrawBalanceRequest
|
||||
{
|
||||
PayoutId = payoutId,
|
||||
@@ -119,10 +128,15 @@ public class WalletService
|
||||
// surface backend error text
|
||||
throw new InvalidOperationException(ex.Status.Detail ?? "خطا در ثبت برداشت", ex);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public async Task<List<WalletWithdrawal>> GetWithdrawalsAsync(int? status = null)
|
||||
{
|
||||
// TODO: Implement when BFF protobuf has GetUserWithdrawals
|
||||
await Task.CompletedTask;
|
||||
return new List<WalletWithdrawal>();
|
||||
/*
|
||||
var request = new GetUserWithdrawalsRequest();
|
||||
if (status.HasValue)
|
||||
{
|
||||
@@ -140,11 +154,15 @@ public class WalletService
|
||||
m.IbanNumber,
|
||||
m.Created.ToDateTime().MiladiToJalaliWithTime()))
|
||||
.ToList();
|
||||
*/
|
||||
}
|
||||
|
||||
public async Task<WithdrawalSettings> GetWithdrawalSettingsAsync()
|
||||
{
|
||||
var response = await _client.GetWithdrawalSettingsAsync(new Empty());
|
||||
return new WithdrawalSettings(response.MinWithdrawalAmount);
|
||||
// TODO: Implement when BFF protobuf has GetWithdrawalSettings
|
||||
await Task.CompletedTask;
|
||||
return new WithdrawalSettings(1_000_000);
|
||||
// var response = await _client.GetWithdrawalSettingsAsync(new Empty());
|
||||
// return new WithdrawalSettings(response.MinWithdrawalAmount);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user