From a6b6a133a4b8f09706dc75a10de82a90844912e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:20:11 +0000 Subject: [PATCH 1/4] Initial plan From bed716731230da5ffd7b0eb0d34098fcfed1faf9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:32:45 +0000 Subject: [PATCH 2/4] Implement Transaction Detail Report changes: hide ID/settlement ref, add Txn No, fix product filter, update headers Co-authored-by: StuartFerguson <16325469+StuartFerguson@users.noreply.github.com> --- .../Pages/Reporting/TransactionDetail.razor | 14 +++++------- .../Reporting/TransactionDetail.razor.cs | 22 +++++++------------ .../Factories/ModelFactory.cs | 1 + .../Models/TransactionModels.cs | 2 ++ .../DataTransferObjects/ContractProduct.cs | 2 ++ .../DataTransferObjects/TransactionDetail.cs | 2 ++ .../Client/APIModelFactory.cs | 2 ++ .../Models/TransactionModels.cs | 1 + 8 files changed, 24 insertions(+), 22 deletions(-) diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Reporting/TransactionDetail.razor b/EstateManagementUI.BlazorServer/Components/Pages/Reporting/TransactionDetail.razor index 2776ccb7..ea87017d 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Reporting/TransactionDetail.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Reporting/TransactionDetail.razor @@ -210,9 +210,6 @@ - @@ -225,6 +222,9 @@ + @@ -232,26 +232,25 @@ Status @GetSortIcon(nameof(TransactionDetailModel.TransactionStatus)) - @foreach (var item in pagedData) { - + - } diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Reporting/TransactionDetail.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Reporting/TransactionDetail.razor.cs index 9f4379d4..f8816eeb 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Reporting/TransactionDetail.razor.cs +++ b/EstateManagementUI.BlazorServer/Components/Pages/Reporting/TransactionDetail.razor.cs @@ -266,9 +266,6 @@ private void SortBy(string columnName) detailData.Transactions = columnName switch { - nameof(TransactionModels.TransactionDetailModel.TransactionId) => _sortAscending - ? detailData.Transactions.OrderBy(t => t.Id).ToList() - : detailData.Transactions.OrderByDescending(t => t.Id).ToList(), nameof(TransactionModels.TransactionDetailModel.TransactionDateTime) => _sortAscending ? detailData.Transactions.OrderBy(t => t.DateTime).ToList() : detailData.Transactions.OrderByDescending(t => t.DateTime).ToList(), @@ -281,6 +278,9 @@ private void SortBy(string columnName) nameof(TransactionModels.TransactionDetailModel.ProductName) => _sortAscending ? detailData.Transactions.OrderBy(t => t.Product).ToList() : detailData.Transactions.OrderByDescending(t => t.Product).ToList(), + nameof(TransactionModels.TransactionDetailModel.TransactionNumber) => _sortAscending + ? detailData.Transactions.OrderBy(t => t.TransactionNumber).ToList() + : detailData.Transactions.OrderByDescending(t => t.TransactionNumber).ToList(), nameof(TransactionModels.TransactionDetailModel.TransactionType) => _sortAscending ? detailData.Transactions.OrderBy(t => t.Type).ToList() : detailData.Transactions.OrderByDescending(t => t.Type).ToList(), @@ -293,9 +293,9 @@ private void SortBy(string columnName) nameof(TransactionModels.TransactionDetailModel.FeesCommission) => _sortAscending ? detailData.Transactions.OrderBy(t => t.TotalFees).ToList() : detailData.Transactions.OrderByDescending(t => t.TotalFees).ToList(), - //nameof(TransactionDetailModel.NetAmount) => _sortAscending - // ? detailData.Transactions.OrderBy(t => t.NetAmount).ToList() - // : detailData.Transactions.OrderByDescending(t => t.NetAmount).ToList(), + nameof(TransactionModels.TransactionDetailModel.NetAmount) => _sortAscending + ? detailData.Transactions.OrderBy(t => t.NetAmount).ToList() + : detailData.Transactions.OrderByDescending(t => t.NetAmount).ToList(), _ => detailData.Transactions }; @@ -310,12 +310,6 @@ private string GetSortIcon(string columnName) return _sortAscending ? "↑" : "↓"; } - private string GetShortId(Guid transactionId) - { - var idString = transactionId.ToString(); - return idString.Length >= 8 ? $"{idString.Substring(0, 8)}..." : idString; - } - private string GetTypeBadgeClass(string? type) { return type switch @@ -347,12 +341,12 @@ private async Task ExportToCSV() var csv = new StringBuilder(); // Header - csv.AppendLine("Transaction ID,Transaction Date & Time,Merchant,Operator,Product,Transaction Type,Transaction Status,Gross Amount,Fees/Commission,Settlement Reference"); + csv.AppendLine("Transaction Date & Time,Merchant,Operator,Product,Txn No,Transaction Type,Transaction Status,Gross Amount,Fees/Commission,Net Amount"); // Data rows foreach (var item in detailData.Transactions) { - csv.AppendLine($"\"{item.Id}\",\"{item.DateTime:yyyy-MM-dd HH:mm:ss}\",\"{item.Merchant}\",\"{item.Operator}\",\"{item.Product}\",\"{item.Type}\",\"{item.Status}\",{item.Value},{item.TotalFees},\"{item.SettlementReference ?? ""}\""); + csv.AppendLine($"\"{item.DateTime:yyyy-MM-dd HH:mm:ss}\",\"{item.Merchant}\",\"{item.Operator}\",\"{item.Product}\",{item.TransactionNumber},\"{item.Type}\",\"{item.Status}\",{item.Value},{item.TotalFees},{item.NetAmount}"); } var fileName = $"transaction-detail-{DateTime.Now:yyyyMMdd-HHmmss}.csv"; diff --git a/EstateManagementUI.BlazorServer/Factories/ModelFactory.cs b/EstateManagementUI.BlazorServer/Factories/ModelFactory.cs index 0fd5f2d4..d88e36b9 100644 --- a/EstateManagementUI.BlazorServer/Factories/ModelFactory.cs +++ b/EstateManagementUI.BlazorServer/Factories/ModelFactory.cs @@ -480,6 +480,7 @@ public static List ConvertFrom(List() }; diff --git a/EstateManagmentUI.BusinessLogic/Models/TransactionModels.cs b/EstateManagmentUI.BusinessLogic/Models/TransactionModels.cs index cf619203..1b4c8c8e 100644 --- a/EstateManagmentUI.BusinessLogic/Models/TransactionModels.cs +++ b/EstateManagmentUI.BusinessLogic/Models/TransactionModels.cs @@ -29,6 +29,7 @@ public class TransactionDetail public Int32 ProductReportingId { get; set; } public String Type { get; set; } public String Status { get; set; } + public Int32 TransactionNumber { get; set; } public Decimal Value { get; set; } public Decimal TotalFees { get; set; } public String SettlementReference { get; set; } From 4dc435a99a2e53def704a814ad4b3f4eff368572 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:43:15 +0000 Subject: [PATCH 3/4] Fix MultiSelectDropdown: close on click-outside via JS interop Co-authored-by: StuartFerguson <16325469+StuartFerguson@users.noreply.github.com> --- .../Shared/MultiSelectDropdown.razor | 40 ++++++++++++++++++- .../wwwroot/js/site.js | 19 +++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/EstateManagementUI.BlazorServer/Components/Shared/MultiSelectDropdown.razor b/EstateManagementUI.BlazorServer/Components/Shared/MultiSelectDropdown.razor index 06df890e..98f57753 100644 --- a/EstateManagementUI.BlazorServer/Components/Shared/MultiSelectDropdown.razor +++ b/EstateManagementUI.BlazorServer/Components/Shared/MultiSelectDropdown.razor @@ -1,5 +1,7 @@ @typeparam TItem @using System.Linq.Expressions +@inject IJSRuntime JSRuntime +@implements IAsyncDisposable
@@ -54,6 +56,7 @@ @code { private ElementReference dropdownRef; private bool isOpen = false; + private DotNetObjectReference>? _dotNetRef; [Parameter] public List? Items { get; set; } @@ -84,6 +87,25 @@ } } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _dotNetRef = DotNetObjectReference.Create(this); + await JSRuntime.InvokeVoidAsync("registerClickOutsideHandler", dropdownRef, _dotNetRef); + } + } + + [JSInvokable] + public void CloseDropdown() + { + if (isOpen) + { + isOpen = false; + StateHasChanged(); + } + } + private void ToggleDropdown() { isOpen = !isOpen; @@ -103,6 +125,20 @@ 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 + public async ValueTask DisposeAsync() + { + try + { + await JSRuntime.InvokeVoidAsync("unregisterClickOutsideHandler", dropdownRef); + } + catch (JSDisconnectedException) + { + // Circuit has already been disconnected — safe to ignore + } + catch (TaskCanceledException) + { + // Component disposed during navigation — safe to ignore + } + _dotNetRef?.Dispose(); + } } diff --git a/EstateManagementUI.BlazorServer/wwwroot/js/site.js b/EstateManagementUI.BlazorServer/wwwroot/js/site.js index 611a246b..64710d4d 100644 --- a/EstateManagementUI.BlazorServer/wwwroot/js/site.js +++ b/EstateManagementUI.BlazorServer/wwwroot/js/site.js @@ -1,3 +1,22 @@ +// Click-outside handler registry for dropdowns +window.registerClickOutsideHandler = function (element, dotNetRef) { + if (!element) return; + var handler = function (event) { + if (!element.contains(event.target)) { + dotNetRef.invokeMethodAsync('CloseDropdown'); + } + }; + document.addEventListener('click', handler); + element._clickOutsideHandler = handler; +}; + +window.unregisterClickOutsideHandler = function (element) { + if (element && element._clickOutsideHandler) { + document.removeEventListener('click', element._clickOutsideHandler); + element._clickOutsideHandler = null; + } +}; + // File download utility function downloadFile(filename, base64Content) { const binaryString = window.atob(base64Content); From 3e7fbf2005a8c8b7eb1cd5c6b9d5f962c9bfd3d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 12:00:09 +0000 Subject: [PATCH 4/4] Fix product dropdown display: remove card overflow-hidden, fix checkbox accent-color Co-authored-by: StuartFerguson <16325469+StuartFerguson@users.noreply.github.com> --- .../Shared/MultiSelectDropdown.razor | 3 ++- EstateManagementUI.BlazorServer/Styles/app.css | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/EstateManagementUI.BlazorServer/Components/Shared/MultiSelectDropdown.razor b/EstateManagementUI.BlazorServer/Components/Shared/MultiSelectDropdown.razor index 98f57753..4d57ebf9 100644 --- a/EstateManagementUI.BlazorServer/Components/Shared/MultiSelectDropdown.razor +++ b/EstateManagementUI.BlazorServer/Components/Shared/MultiSelectDropdown.razor @@ -40,7 +40,8 @@ + class="h-4 w-4 rounded border-gray-300 cursor-pointer" + style="accent-color: var(--color-admin-primary);" /> @GetItemText(item) } diff --git a/EstateManagementUI.BlazorServer/Styles/app.css b/EstateManagementUI.BlazorServer/Styles/app.css index 295bc55b..e578d49a 100644 --- a/EstateManagementUI.BlazorServer/Styles/app.css +++ b/EstateManagementUI.BlazorServer/Styles/app.css @@ -2,6 +2,16 @@ @tailwind components; @tailwind utilities; +/* Design-system CSS custom properties (available throughout the app) */ +:root { + --color-admin-primary: #007bff; + --color-admin-secondary: #6c757d; + --color-admin-success: #28a745; + --color-admin-info: #17a2b8; + --color-admin-warning: #ffc107; + --color-admin-danger: #dc3545; +} + /* AdminLTE-inspired components */ @layer components { /* Navigation Menu Items */ @@ -90,11 +100,13 @@ /* Cards */ .card { - @apply bg-white rounded-lg shadow-admin overflow-hidden; + @apply bg-white rounded-lg shadow-admin; + /* NOTE: overflow-hidden is intentionally omitted so that absolutely-positioned + children (e.g. multi-select dropdown panels) can escape the card boundary. */ } .card-header { - @apply px-6 py-4 border-b border-admin-border bg-white; + @apply px-6 py-4 border-b border-admin-border bg-white rounded-t-lg overflow-hidden; } .card-body { @@ -102,7 +114,7 @@ } .card-footer { - @apply px-6 py-4 border-t border-admin-border bg-gray-50; + @apply px-6 py-4 border-t border-admin-border bg-gray-50 rounded-b-lg overflow-hidden; } .card-title {
- Transaction ID @GetSortIcon(nameof(TransactionDetailModel.TransactionId)) - Date & Time @GetSortIcon(nameof(TransactionDetailModel.TransactionDateTime)) Product @GetSortIcon(nameof(TransactionDetailModel.ProductName)) + Txn No @GetSortIcon(nameof(TransactionDetailModel.TransactionNumber)) + Type @GetSortIcon(nameof(TransactionDetailModel.TransactionType)) - Gross Amount @GetSortIcon(nameof(TransactionDetailModel.GrossAmount)) + Gross @GetSortIcon(nameof(TransactionDetailModel.GrossAmount)) Fees @GetSortIcon(nameof(TransactionDetailModel.FeesCommission)) - Net Amount @GetSortIcon(nameof(TransactionDetailModel.NetAmount)) + Net @GetSortIcon(nameof(TransactionDetailModel.NetAmount)) Settlement Ref
@GetShortId(item.Id) @item.DateTime.ToString("yyyy-MM-dd HH:mm:ss") @item.Merchant @item.Operator @item.Product@item.TransactionNumber @item.Type @@ -265,7 +264,6 @@ @item.Value.ToString("C") @item.TotalFees.ToString("C") @item.NetAmount.ToString("C")@(item.SettlementReference ?? "-")