@page "/Rewards/Manage" @using BlazorFrontend.Services @using LibraryManagement.Shared.Models @using Microsoft.AspNetCore.Authorization @inject LibraryApiClient ApiClient @inject IJSRuntime JS @attribute [Authorize(Roles = "Librarian")] Rewards Management

Rewards Management

Fulfill pending redemptions and review all members' loyalty activity.

Pending

@_pendingRedemptions.Count()

Members w/ History

@_allMembersHistory.Count()

@if (_isLoading) {
} else { @if (_activeTab == "pending") {
@if (!_pendingRedemptions.Any()) {

No pending redemptions. All caught up!

} else {
@foreach (var r in _pendingRedemptions.OrderBy(x => x.RedeemedAt)) { }
Member Reward Points Claimed Status Action
@(r.ExternalUserId.Length >= 2 ? r.ExternalUserId.Substring(0, 2).ToUpper() : "U")
@r.ExternalUserId

@r.RewardName

ID: @(r.Id.Length > 8 ? r.Id[..8] : r.Id)...

@r.PointCost.ToString("N0") pts @r.RedeemedAt.ToString("MMM dd, yyyy") Pending
}
} else if (_activeTab == "history") {
@if (!_allMembersHistory.Any()) {

No loyalty history found for any members yet.

} else {
@foreach (var member in FilteredMembers) {
@(member.UserName.Length >= 2 ? member.UserName.Substring(0, 2).ToUpper() : "?")

@member.UserName

@member.UserEmail

Balance

@member.CurrentBalance.ToString("N0") pts

Tier

@member.Tier

Transactions

@member.History.Count()

@if (_expandedMembers.Contains(member.AccountId)) {
@if (!member.History.Any()) {

No transactions found for this member.

} else { @foreach (var h in member.History.OrderByDescending(x => x.CreatedAt)) { var isPos = h.PointDelta >= 0; var label = PrettyEvent(h.EventKey, h.PointDelta); string? spentOn = h.RewardName; }
Date Type Details Spent on Points
@h.CreatedAt.ToString("MMM dd, yyyy") @label @h.Description @if (!string.IsNullOrWhiteSpace(spentOn)) { @spentOn } else { } @(isPos ? "+" : "")@h.PointDelta.ToString("N0")
}
}
}
}
} } @code { private string _activeTab = "pending"; private bool _isLoading = true; private string _searchQuery = ""; private IEnumerable _pendingRedemptions = Enumerable.Empty(); private IEnumerable _allMembersHistory = Enumerable.Empty(); private HashSet _expandedMembers = new(); protected override async Task OnInitializedAsync() { await LoadDataAsync(); } private async Task LoadDataAsync() { _isLoading = true; try { _pendingRedemptions = await ApiClient.GetPendingRedemptionsAsync(); _allMembersHistory = await ApiClient.GetAllMembersPointsHistoryAsync(); } catch (Exception ex) { Console.WriteLine($"Error loading management data: {ex.Message}"); } finally { _isLoading = false; } } private IEnumerable FilteredMembers => string.IsNullOrWhiteSpace(_searchQuery) ? _allMembersHistory : _allMembersHistory.Where(m => m.UserName.Contains(_searchQuery, StringComparison.OrdinalIgnoreCase) || m.UserEmail.Contains(_searchQuery, StringComparison.OrdinalIgnoreCase)); private void ToggleMember(string accountId) { if (_expandedMembers.Contains(accountId)) _expandedMembers.Remove(accountId); else _expandedMembers.Add(accountId); } private async Task FulfillAsync(LoyaltyRedemptionDto redemption) { bool confirmed = await JS.InvokeAsync("confirm", "Fulfill this redemption and grant membership to the member?"); if (!confirmed) return; var result = await ApiClient.FulfillRedemptionAsync(redemption.Id); if (result.Success) { await JS.InvokeVoidAsync("showToast", result.Message, "success"); await LoadDataAsync(); } else { await JS.InvokeVoidAsync("showToast", result.Message, "error"); } } private string PrettyEvent(string? eventKey, double points) { var key = (eventKey ?? string.Empty).Trim().ToUpperInvariant(); return key switch { "RETURN" or "BOOK_RETURN" or "BORROW_RETURN" => "RETURN", "SIGNUP" or "REGISTER" => "SIGNUP", "SUBSCRIBE" or "SUBSCRIPTION" or "MEMBERSHIP" => "SUBSCRIBE", "REDEEM" or "REDEMPTION" or "CLAIM_REWARD" => "REDEEM", _ => points < 0 ? "SPENT" : (string.IsNullOrEmpty(key) ? "EARNED" : key) }; } }