Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@page "/reporting/analytical-charts"
@using EstateManagementUI.BlazorServer.Factories
@using EstateManagementUI.BusinessLogic.Requests
@using Shared.General
@using global::Shared.General
@rendermode InteractiveServer
@inject IMediator Mediator
@inject IJSRuntime JSRuntime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@rendermode InteractiveServer
@using System.Text
@using EstateManagementUI.BlazorServer.Factories
@using EstateManagementUI.BlazorServer.Components.Shared
@using EstateManagementUI.BusinessLogic.Requests
@inject IMediator Mediator
@inject NavigationManager Navigation
Expand Down Expand Up @@ -71,48 +72,42 @@
<!-- Merchant Filter -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Merchant</label>
<select @bind="_selectedMerchantId" class="form-control">
<option value="">All Merchants</option>
@if (merchants != null)
{
@foreach (var merchant in merchants)
{
<option value="@merchant.MerchantId">@merchant.MerchantName</option>
}
}
</select>
<MultiSelectDropdown TItem="MerchantModel"
Items="merchants"
SelectedItems="_selectedMerchantIds"
SelectedItemsChanged="OnMerchantSelectionChanged"
GetItemId="m => m.MerchantId.ToString()"
GetItemText="m => m.MerchantName ?? string.Empty"
Placeholder="All Merchants"
AriaLabel="Select one or more merchants" />
</div>

<!-- Operator Filter -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Operator</label>
<select @bind="_selectedOperatorId" class="form-control">
<option value="">All Operators</option>
@if (operators != null)
{
@foreach (var op in operators)
{
<option value="@op.OperatorId">@op.Name</option>
}
}
</select>
<MultiSelectDropdown TItem="OperatorModel"
Items="operators"
SelectedItems="_selectedOperatorIds"
SelectedItemsChanged="OnOperatorSelectionChanged"
GetItemId="o => o.OperatorId.ToString()"
GetItemText="o => o.Name ?? string.Empty"
Placeholder="All Operators"
AriaLabel="Select one or more operators" />
</div>
</div>

<!-- Product Filter (second row) -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mt-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Product</label>
<select @bind="_selectedProductId" class="form-control">
<option value="">All Products</option>
@if (products != null)
{
@foreach (var product in products)
{
<option value="@product.ContractProductId">@product.ProductName</option>
}
}
</select>
<MultiSelectDropdown TItem="ContractProductModel"
Items="products"
SelectedItems="_selectedProductIds"
SelectedItemsChanged="OnProductSelectionChanged"
GetItemId="p => p.ContractProductId.ToString()"
GetItemText="p => p.ProductName ?? string.Empty"
Placeholder="All Products"
AriaLabel="Select one or more products" />
</div>
</div>

Expand Down Expand Up @@ -330,9 +325,9 @@
// Filter states
private DateOnly _startDate = DateOnly.FromDateTime(DateTime.Now.AddDays(-7));
private DateOnly _endDate = DateOnly.FromDateTime(DateTime.Now);
private string _selectedMerchantId = "";
private string _selectedOperatorId = "";
private string _selectedProductId = "";
private List<string> _selectedMerchantIds = new List<string>();
private List<string> _selectedOperatorIds = new List<string>();
private List<string> _selectedProductIds = new List<string>();

// Data
private List<TransactionDetailModel>? detailData;
Expand Down Expand Up @@ -396,7 +391,7 @@

if (!string.IsNullOrEmpty(query["merchantId"]))
{
_selectedMerchantId = query["merchantId"];
_selectedMerchantIds = new List<string> { query["merchantId"] };
}

if (!string.IsNullOrEmpty(query["startDate"]))
Expand Down Expand Up @@ -479,19 +474,20 @@
var startDate = _startDate.ToDateTime(TimeOnly.MinValue);
var endDate = _endDate.ToDateTime(TimeOnly.MaxValue);

Guid? merchantId = string.IsNullOrEmpty(_selectedMerchantId) ? null : Guid.Parse(_selectedMerchantId);
Guid? operatorId = string.IsNullOrEmpty(_selectedOperatorId) ? null : Guid.Parse(_selectedOperatorId);
Guid? productId = string.IsNullOrEmpty(_selectedProductId) ? null : Guid.Parse(_selectedProductId);
// Parse selected IDs with error handling
var merchantIds = ParseGuidList(_selectedMerchantIds);
var operatorIds = ParseGuidList(_selectedOperatorIds);
var productIds = ParseGuidList(_selectedProductIds);

var result = await Mediator.Send(new Queries.GetTransactionDetailQuery(
correlationId,
accessToken,
estateId,
startDate,
endDate,
merchantId,
operatorId,
productId
merchantIds,
operatorIds,
productIds
));

if (result.IsSuccess && result.Data != null)
Expand All @@ -511,6 +507,21 @@
}
}

private List<Guid>? ParseGuidList(List<string> stringIds)
{
if (!stringIds.Any()) return null;

var guids = new List<Guid>();
foreach (var id in stringIds)
{
if (Guid.TryParse(id, out var guid))
{
guids.Add(guid);
}
}
return guids.Any() ? guids : null;
}

private void CalculateKPIs()
{
if (detailData == null || !detailData.Any())
Expand Down Expand Up @@ -538,9 +549,9 @@
{
_startDate = DateOnly.FromDateTime(DateTime.Now.AddDays(-7));
_endDate = DateOnly.FromDateTime(DateTime.Now);
_selectedMerchantId = "";
_selectedOperatorId = "";
_selectedProductId = "";
_selectedMerchantIds = new List<string>();
_selectedOperatorIds = new List<string>();
_selectedProductIds = new List<string>();
_currentPage = 1; // Reset to first page when filters are cleared
await LoadDetailData();
}
Expand Down Expand Up @@ -693,4 +704,19 @@

return "Back to Reporting";
}

private void OnMerchantSelectionChanged(List<string> selectedIds)
{
_selectedMerchantIds = selectedIds;
}

private void OnOperatorSelectionChanged(List<string> selectedIds)
{
_selectedOperatorIds = selectedIds;
}

private void OnProductSelectionChanged(List<string> selectedIds)
{
_selectedProductIds = selectedIds;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
@typeparam TItem
@using System.Linq.Expressions

<div class="relative" @ref="dropdownRef">
<!-- Dropdown Button -->
<button type="button"
@onclick="ToggleDropdown"
class="form-control flex items-center justify-between cursor-pointer"
aria-label="@AriaLabel"
aria-expanded="@isOpen">
<span class="truncate">
@if (SelectedItems.Any())
{
<text>@SelectedItems.Count item@(SelectedItems.Count != 1 ? "s" : "") selected</text>
}
else
{
<text>@Placeholder</text>
}
</span>
<svg class="w-4 h-4 ml-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>

<!-- Dropdown Menu -->
@if (isOpen)
{
<div class="absolute z-50 mt-1 w-full bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto">
@if (Items != null && Items.Any())
{
@foreach (var item in Items)
{
var itemId = GetItemId(item);
var isSelected = SelectedItems.Contains(itemId);

<label class="flex items-center px-3 py-2 hover:bg-gray-100 cursor-pointer">
<input type="checkbox"
checked="@isSelected"
@onchange="@(() => ToggleItem(itemId))"
class="form-checkbox h-4 w-4 text-admin-primary rounded border-gray-300 focus:ring-admin-primary" />
<span class="ml-2 text-sm text-gray-700">@GetItemText(item)</span>
</label>
}
}
else
{
<div class="px-3 py-2 text-sm text-gray-500">No items available</div>
}
</div>
}
</div>

@code {
private ElementReference dropdownRef;
private bool isOpen = false;

[Parameter]
public List<TItem>? Items { get; set; }

[Parameter]
public List<string> SelectedItems { get; set; } = new List<string>();

[Parameter]
public EventCallback<List<string>> SelectedItemsChanged { get; set; }

[Parameter]
public Func<TItem, string> GetItemId { get; set; } = item => item?.ToString() ?? "";

[Parameter]
public Func<TItem, string> GetItemText { get; set; } = item => item?.ToString() ?? "";

[Parameter]
public string Placeholder { get; set; } = "Select items...";

[Parameter]
public string AriaLabel { get; set; } = "Select items";

protected override void OnInitialized()
{
if (SelectedItems == null)
{
SelectedItems = new List<string>();
}
}

private void ToggleDropdown()
{
isOpen = !isOpen;
}

private async Task ToggleItem(string itemId)
{
if (SelectedItems.Contains(itemId))
{
SelectedItems.Remove(itemId);
}
else
{
SelectedItems.Add(itemId);
}

await SelectedItemsChanged.InvokeAsync(SelectedItems);
}

// Close dropdown when clicking outside (would need JS interop for full implementation)
// For now, we'll keep it simple and let users close by clicking the button again
}
3 changes: 3 additions & 0 deletions EstateManagementUI.BlazorServer/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AppSettings": {
"TestMode": "Full"
}
}
18 changes: 10 additions & 8 deletions EstateManagmentUI.BusinessLogic/Client/StubTestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -560,22 +560,24 @@ public static List<TransactionDetailModel> GetMockTransactionDetails(Queries.Get
}

// Apply filters
if (query.MerchantId.HasValue)
IEnumerable<TransactionDetailModel> filteredTransactions = transactions;

if (query.MerchantIds != null && query.MerchantIds.Any())
{
transactions = transactions.Where(t => t.MerchantId == query.MerchantId.Value).ToList();
filteredTransactions = filteredTransactions.Where(t => query.MerchantIds.Contains(t.MerchantId));
}

if (query.OperatorId.HasValue)
if (query.OperatorIds != null && query.OperatorIds.Any())
{
transactions = transactions.Where(t => t.OperatorId == query.OperatorId.Value).ToList();
filteredTransactions = filteredTransactions.Where(t => query.OperatorIds.Contains(t.OperatorId));
}

if (query.ProductId.HasValue)
if (query.ProductIds != null && query.ProductIds.Any())
{
transactions = transactions.Where(t => t.ProductId == query.ProductId.Value).ToList();
filteredTransactions = filteredTransactions.Where(t => query.ProductIds.Contains(t.ProductId));
}

// Sort by transaction date descending (most recent first)
return transactions.OrderByDescending(t => t.TransactionDateTime).ToList();
// Sort by transaction date descending (most recent first) and materialize
return filteredTransactions.OrderByDescending(t => t.TransactionDateTime).ToList();
}
}
2 changes: 1 addition & 1 deletion EstateManagmentUI.BusinessLogic/Requests/Requests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public record GetProductPerformanceQuery(CorrelationId CorrelationId, string Acc
public record GetOperatorTransactionSummaryQuery(CorrelationId CorrelationId, string AccessToken, Guid EstateId, DateTime StartDate, DateTime EndDate, Guid? MerchantId = null, Guid? OperatorId = null) : IRequest<Result<List<OperatorTransactionSummaryModel>>>;
public record GetMerchantSettlementHistoryQuery(CorrelationId CorrelationId, string AccessToken, Guid EstateId, Guid? MerchantId, DateTime StartDate, DateTime EndDate) : IRequest<Result<List<MerchantSettlementHistoryModel>>>;
public record GetSettlementSummaryQuery(CorrelationId CorrelationId, string AccessToken, Guid EstateId, DateTime StartDate, DateTime EndDate, Guid? MerchantId = null, string? Status = null) : IRequest<Result<List<SettlementSummaryModel>>>;
public record GetTransactionDetailQuery(CorrelationId CorrelationId, string AccessToken, Guid EstateId, DateTime StartDate, DateTime EndDate, Guid? MerchantId = null, Guid? OperatorId = null, Guid? ProductId = null) : IRequest<Result<List<TransactionDetailModel>>>;
public record GetTransactionDetailQuery(CorrelationId CorrelationId, string AccessToken, Guid EstateId, DateTime StartDate, DateTime EndDate, List<Guid>? MerchantIds = null, List<Guid>? OperatorIds = null, List<Guid>? ProductIds = null) : IRequest<Result<List<TransactionDetailModel>>>;
}

public static class Commands
Expand Down
18 changes: 10 additions & 8 deletions EstateManagmentUI.BusinessLogic/Services/TestMediatorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -914,22 +914,24 @@ private List<TransactionDetailModel> GetMockTransactionDetails(Queries.GetTransa
}

// Apply filters
if (query.MerchantId.HasValue)
IEnumerable<TransactionDetailModel> filteredTransactions = transactions;

if (query.MerchantIds != null && query.MerchantIds.Any())
{
transactions = transactions.Where(t => t.MerchantId == query.MerchantId.Value).ToList();
filteredTransactions = filteredTransactions.Where(t => query.MerchantIds.Contains(t.MerchantId));
}

if (query.OperatorId.HasValue)
if (query.OperatorIds != null && query.OperatorIds.Any())
{
transactions = transactions.Where(t => t.OperatorId == query.OperatorId.Value).ToList();
filteredTransactions = filteredTransactions.Where(t => query.OperatorIds.Contains(t.OperatorId));
}

if (query.ProductId.HasValue)
if (query.ProductIds != null && query.ProductIds.Any())
{
transactions = transactions.Where(t => t.ProductId == query.ProductId.Value).ToList();
filteredTransactions = filteredTransactions.Where(t => query.ProductIds.Contains(t.ProductId));
}

// Sort by transaction date descending (most recent first)
return transactions.OrderByDescending(t => t.TransactionDateTime).ToList();
// Sort by transaction date descending (most recent first) and materialize
return filteredTransactions.OrderByDescending(t => t.TransactionDateTime).ToList();
}
}
Loading