Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,6 @@
<table class="table">
<thead>
<tr>
<th @onclick="() => SortBy(nameof(TransactionDetailModel.TransactionId))" class="cursor-pointer hover:bg-gray-50">
Transaction ID @GetSortIcon(nameof(TransactionDetailModel.TransactionId))
</th>
<th @onclick="() => SortBy(nameof(TransactionDetailModel.TransactionDateTime))" class="cursor-pointer hover:bg-gray-50">
Date & Time @GetSortIcon(nameof(TransactionDetailModel.TransactionDateTime))
</th>
Expand All @@ -225,33 +222,35 @@
<th @onclick="() => SortBy(nameof(TransactionDetailModel.ProductName))" class="cursor-pointer hover:bg-gray-50">
Product @GetSortIcon(nameof(TransactionDetailModel.ProductName))
</th>
<th @onclick="() => SortBy(nameof(TransactionDetailModel.TransactionNumber))" class="cursor-pointer hover:bg-gray-50">
Txn No @GetSortIcon(nameof(TransactionDetailModel.TransactionNumber))
</th>
<th @onclick="() => SortBy(nameof(TransactionDetailModel.TransactionType))" class="cursor-pointer hover:bg-gray-50">
Type @GetSortIcon(nameof(TransactionDetailModel.TransactionType))
</th>
<th @onclick="() => SortBy(nameof(TransactionDetailModel.TransactionStatus))" class="cursor-pointer hover:bg-gray-50">
Status @GetSortIcon(nameof(TransactionDetailModel.TransactionStatus))
</th>
<th @onclick="() => SortBy(nameof(TransactionDetailModel.GrossAmount))" class="!text-right cursor-pointer hover:bg-gray-50">
Gross Amount @GetSortIcon(nameof(TransactionDetailModel.GrossAmount))
Gross @GetSortIcon(nameof(TransactionDetailModel.GrossAmount))
</th>
<th @onclick="() => SortBy(nameof(TransactionDetailModel.FeesCommission))" class="!text-right cursor-pointer hover:bg-gray-50">
Fees @GetSortIcon(nameof(TransactionDetailModel.FeesCommission))
</th>
<th @onclick="() => SortBy(nameof(TransactionDetailModel.NetAmount))" class="!text-right cursor-pointer hover:bg-gray-50">
Net Amount @GetSortIcon(nameof(TransactionDetailModel.NetAmount))
Net @GetSortIcon(nameof(TransactionDetailModel.NetAmount))
</th>
<th>Settlement Ref</th>
</tr>
</thead>
<tbody>
@foreach (var item in pagedData)
{
<tr>
<td class="font-mono text-xs">@GetShortId(item.Id)</td>
<td class="text-sm">@item.DateTime.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td class="font-medium">@item.Merchant</td>
<td>@item.Operator</td>
<td>@item.Product</td>
<td class="font-mono text-xs">@item.TransactionNumber</td>
<td>
<span class="badge @GetTypeBadgeClass(item.Type)">
@item.Type
Expand All @@ -265,7 +264,6 @@
<td class="text-right">@item.Value.ToString("C")</td>
<td class="text-right text-red-600">@item.TotalFees.ToString("C")</td>
<td class="text-right font-medium">@item.NetAmount.ToString("C")</td>
<td class="text-sm text-gray-600">@(item.SettlementReference ?? "-")</td>
</tr>
}
</tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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(),
Expand All @@ -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
};

Expand All @@ -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
Expand Down Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@typeparam TItem
@using System.Linq.Expressions
@inject IJSRuntime JSRuntime
@implements IAsyncDisposable

<div class="relative" @ref="dropdownRef">
<!-- Dropdown Button -->
Expand Down Expand Up @@ -38,7 +40,8 @@
<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" />
class="h-4 w-4 rounded border-gray-300 cursor-pointer"
style="accent-color: var(--color-admin-primary);" />
<span class="ml-2 text-sm text-gray-700">@GetItemText(item)</span>
</label>
}
Expand All @@ -54,6 +57,7 @@
@code {
private ElementReference dropdownRef;
private bool isOpen = false;
private DotNetObjectReference<MultiSelectDropdown<TItem>>? _dotNetRef;

[Parameter]
public List<TItem>? Items { get; set; }
Expand Down Expand Up @@ -84,6 +88,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;
Expand All @@ -103,6 +126,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();
}
}
1 change: 1 addition & 0 deletions EstateManagementUI.BlazorServer/Factories/ModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ public static List<ContractDropDownModel> ConvertFrom(List<BusinessLogic.Models.
Value = resultDataTransaction.Value,
Status = resultDataTransaction.Status,
Type = resultDataTransaction.Type,
TransactionNumber = resultDataTransaction.TransactionNumber,
ProductReportingId = resultDataTransaction.ProductReportingId,
ProductId = resultDataTransaction.ProductId,
Product = resultDataTransaction.Product,
Expand Down
2 changes: 2 additions & 0 deletions EstateManagementUI.BlazorServer/Models/TransactionModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,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 Decimal NetAmount { get; set; }
Expand Down Expand Up @@ -168,6 +169,7 @@ public class TransactionDetailModel
public Guid OperatorId { get; set; }
public string? ProductName { get; set; }
public Guid ProductId { get; set; }
public int TransactionNumber { get; set; }
public string? TransactionType { get; set; } // sale, refund, reversal
public string? TransactionStatus { get; set; } // successful, failed, reversed
public decimal GrossAmount { get; set; }
Expand Down
18 changes: 15 additions & 3 deletions EstateManagementUI.BlazorServer/Styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -90,19 +100,21 @@

/* 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 {
@apply p-6;
}

.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 {
Expand Down
19 changes: 19 additions & 0 deletions EstateManagementUI.BlazorServer/wwwroot/js/site.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public class ContractProduct
public Guid ContractId { get; set; }
[JsonProperty("product_id")]
public Guid ProductId { get; set; }
[JsonProperty("product_reporting_id")]
public Int32 ProductReportingId { get; set; }
[JsonProperty("product_name")]
public String ProductName { get; set; }
[JsonProperty("display_text")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class TransactionDetail
public String Type { get; set; }
[JsonProperty("status")]
public String Status { get; set; }
[JsonProperty("transaction_number")]
public Int32 TransactionNumber { get; set; }
[JsonProperty("value")]
public Decimal Value { get; set; }
[JsonProperty("total_fees")]
Expand Down
2 changes: 2 additions & 0 deletions EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public static TransactionModels.TransactionDetailReportResponse ConvertFrom(Tran
ProductReportingId = transaction.ProductReportingId,
Type = transaction.Type,
Status = transaction.Status,
TransactionNumber = transaction.TransactionNumber,
Value = transaction.Value,
TotalFees = transaction.TotalFees,
SettlementReference = transaction.SettlementReference
Expand Down Expand Up @@ -499,6 +500,7 @@ public static ContractModels.ContractModel ToContract(this Contract apiResultDat
DisplayText = contractProduct.DisplayText,
ProductName = contractProduct.ProductName,
ContractProductId = contractProduct.ProductId,
ContractProductReportingId = contractProduct.ProductReportingId,
NumberOfFees = contractProduct.TransactionFees.Count,
TransactionFees = new List<ContractModels.ContractProductTransactionFeeModel>()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
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; }
Expand Down Expand Up @@ -104,7 +105,7 @@

public class ProductPerformanceDetail
{
public String ProductName { get; set; }

Check warning on line 108 in EstateManagmentUI.BusinessLogic/Models/TransactionModels.cs

View workflow job for this annotation

GitHub Actions / Build and Unit Test

Non-nullable property 'ProductName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public Guid ProductId { get; set; }
public Int32 ProductReportingId { get; set; }
public Guid ContractId { get; set; }
Expand Down
Loading