diff --git a/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj b/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj index 08e3531c..335d3e57 100644 --- a/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj +++ b/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj @@ -4,7 +4,7 @@ net10.0 enable enable - None + Full false true diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsIndexPageTests.cs index 9e2c3d58..39864baa 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsIndexPageTests.cs @@ -1,6 +1,7 @@ using Bunit; using EstateManagementUI.BlazorServer.Components.Permissions; using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using EstateManagementUI.BusinessLogic.Models; using EstateManagementUI.BusinessLogic.Requests; using MediatR; @@ -13,28 +14,8 @@ namespace EstateManagementUI.BlazorServer.Tests.Pages.Contracts; -public class ContractsIndexPageTests : TestContext +public class ContractsIndexPageTests : BaseTest { - private readonly Mock _mockMediator; - private readonly Mock _mockPermissionKeyProvider; - private readonly Mock _mockNavigationManager; - - public ContractsIndexPageTests() - { - _mockMediator = new Mock(); - _mockPermissionKeyProvider = new Mock(); - _mockNavigationManager = new Mock(); - - _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); - - Services.AddSingleton(_mockMediator.Object); - Services.AddSingleton(_mockPermissionKeyProvider.Object); - Services.AddSingleton(_mockNavigationManager.Object); - - // Add required permission components - ComponentFactories.AddStub(); - } - [Fact] public void ContractsIndex_RendersCorrectly() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsViewPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsViewPageTests.cs index 7ed4edbb..180bd681 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsViewPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsViewPageTests.cs @@ -2,6 +2,7 @@ using EstateManagementUI.BlazorServer.Components.Pages.Contracts; using EstateManagementUI.BlazorServer.Components.Permissions; using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using EstateManagementUI.BusinessLogic.Models; using EstateManagementUI.BusinessLogic.Requests; using MediatR; @@ -13,27 +14,8 @@ namespace EstateManagementUI.BlazorServer.Tests.Pages.Contracts; -public class ContractsViewPageTests : TestContext +public class ContractsViewPageTests : BaseTest { - private readonly Mock _mockMediator; - private readonly Mock _mockNavigationManager; - private readonly Mock _mockPermissionKeyProvider; - - public ContractsViewPageTests() - { - _mockMediator = new Mock(); - _mockNavigationManager = new Mock(); - _mockPermissionKeyProvider = new Mock(); - - _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); - - Services.AddSingleton(_mockMediator.Object); - Services.AddSingleton(_mockNavigationManager.Object); - Services.AddSingleton(_mockPermissionKeyProvider.Object); - - ComponentFactories.AddStub(); - } - [Fact] public void ContractsView_RendersCorrectly() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs index 8cdea50b..9e6b095b 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs @@ -1,25 +1,21 @@ using Bunit; +using EstateManagementUI.BlazorServer.Permissions; using EstateManagementUI.BusinessLogic.Models; using EstateManagementUI.BusinessLogic.Requests; using MediatR; +using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using Moq; using Shouldly; using SimpleResults; +using System.Security.Claims; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using EstateIndex = EstateManagementUI.BlazorServer.Components.Pages.Estate.Index; namespace EstateManagementUI.BlazorServer.Tests.Pages.Estate; -public class EstateIndexPageTests : TestContext +public class EstateIndexPageTests : BaseTest { - private readonly Mock _mockMediator; - - public EstateIndexPageTests() - { - _mockMediator = new Mock(); - Services.AddSingleton(_mockMediator.Object); - } - [Fact] public void EstateIndex_RendersCorrectly() { @@ -55,24 +51,25 @@ public void EstateIndex_DisplaysEstateDetails() { EstateId = Guid.NewGuid(), EstateName = "Test Estate", - Reference = "EST001" + Reference = "EST001", + Operators = new List() }; - + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) .ReturnsAsync(Result.Success(estate)); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); _mockMediator.Setup(x => x.Send(It.IsAny(), default)) .ReturnsAsync(Result.Success(new List())); _mockMediator.Setup(x => x.Send(It.IsAny(), default)) .ReturnsAsync(Result.Success(new List())); - + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(new List())); // Act var cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); - + //cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + // Assert - cut.Markup.ShouldContain("Test Estate"); + //cut.Markup.ShouldContain("Test Estate"); + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Test Estate"), timeout: TimeSpan.FromSeconds(5)); } [Fact] diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/FileProcessing/FileProcessingIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/FileProcessing/FileProcessingIndexPageTests.cs index b90f6996..25912bb9 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/FileProcessing/FileProcessingIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/FileProcessing/FileProcessingIndexPageTests.cs @@ -1,40 +1,59 @@ using Bunit; +using Bunit.TestDoubles; using EstateManagementUI.BlazorServer.Components.Permissions; using EstateManagementUI.BlazorServer.Models; using EstateManagementUI.BlazorServer.Permissions; using EstateManagementUI.BusinessLogic.Requests; using MediatR; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using Moq; using Shouldly; using SimpleResults; +using System.Security.Claims; using FileImportLogModel = EstateManagementUI.BusinessLogic.Models.FileImportLogModel; using FileProcessingIndex = EstateManagementUI.BlazorServer.Components.Pages.FileProcessing.Index; namespace EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; -public class FileProcessingIndexPageTests : TestContext -{ - private readonly Mock _mockMediator; - private readonly Mock _mockNavigationManager; - private readonly Mock _mockPermissionKeyProvider; - - public FileProcessingIndexPageTests() - { +public abstract class BaseTest :TestContext { + protected BaseTest() { _mockMediator = new Mock(); _mockNavigationManager = new Mock(); _mockPermissionKeyProvider = new Mock(); - + _mockAuthStateProvider = new Mock(); + _mockPermissionService = new Mock(); + _mockPermissionStore = new Mock(); + _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); - + _mockPermissionService.Setup(x => x.HasPermissionAsync(It.IsAny(), It.IsAny())).ReturnsAsync(true); + Services.AddSingleton(_mockMediator.Object); Services.AddSingleton(_mockNavigationManager.Object); Services.AddSingleton(_mockPermissionKeyProvider.Object); - + Services.AddSingleton(_mockPermissionService.Object); + Services.AddSingleton(_mockAuthStateProvider.Object); + Services.AddSingleton(_mockPermissionStore.Object); + + // Add required permission components ComponentFactories.AddStub(); + ComponentFactories.AddStub(); + + var claims = new[] { new Claim(ClaimTypes.Role, "Estate"), new Claim("estateId", Guid.NewGuid().ToString()), new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "EstateUser") }; + this.AddTestAuthorization().SetClaims(claims); } + protected readonly Mock _mockMediator; + protected readonly Mock _mockNavigationManager; + protected readonly Mock _mockPermissionKeyProvider; + protected readonly Mock _mockPermissionService; + protected readonly Mock _mockAuthStateProvider; + protected readonly Mock _mockPermissionStore; +} + +public class FileProcessingIndexPageTests : BaseTest +{ [Fact] public void FileProcessingIndex_RendersCorrectly() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs index 2ec10e7d..6856f846 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs @@ -1,6 +1,7 @@ using Bunit; using EstateManagementUI.BlazorServer.Components.Permissions; using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using EstateManagementUI.BusinessLogic.Models; using EstateManagementUI.BusinessLogic.Requests; using MediatR; @@ -13,28 +14,8 @@ namespace EstateManagementUI.BlazorServer.Tests.Pages.Merchants; -public class MerchantsIndexPageTests : TestContext +public class MerchantsIndexPageTests : BaseTest { - private readonly Mock _mockMediator; - private readonly Mock _mockPermissionKeyProvider; - private readonly Mock _mockNavigationManager; - - public MerchantsIndexPageTests() - { - _mockMediator = new Mock(); - _mockPermissionKeyProvider = new Mock(); - _mockNavigationManager = new Mock(); - - _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); - - Services.AddSingleton(_mockMediator.Object); - Services.AddSingleton(_mockPermissionKeyProvider.Object); - Services.AddSingleton(_mockNavigationManager.Object); - - // Add required permission components - ComponentFactories.AddStub(); - } - [Fact] public void MerchantsIndex_InitialState_ShowsLoadingIndicator() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs index 68246d84..ba42adc2 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs @@ -1,6 +1,7 @@ using Bunit; using EstateManagementUI.BlazorServer.Components.Pages.Merchants; using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using EstateManagementUI.BusinessLogic.Requests; using MediatR; using Microsoft.AspNetCore.Components; @@ -12,20 +13,8 @@ namespace EstateManagementUI.BlazorServer.Tests.Pages.Merchants; -public class MerchantsViewPageTests : TestContext +public class MerchantsViewPageTests : BaseTest { - private readonly Mock _mockMediator; - private readonly Mock _mockNavigationManager; - - public MerchantsViewPageTests() - { - _mockMediator = new Mock(); - _mockNavigationManager = new Mock(); - - Services.AddSingleton(_mockMediator.Object); - Services.AddSingleton(_mockNavigationManager.Object); - } - [Fact] public void MerchantsView_InitialState_ShowsLoadingIndicator() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsIndexPageTests.cs index 6fb058d7..d891e2be 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsIndexPageTests.cs @@ -1,6 +1,7 @@ using Bunit; using EstateManagementUI.BlazorServer.Components.Permissions; using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using EstateManagementUI.BusinessLogic.Models; using EstateManagementUI.BusinessLogic.Requests; using MediatR; @@ -13,28 +14,8 @@ namespace EstateManagementUI.BlazorServer.Tests.Pages.Operators; -public class OperatorsIndexPageTests : TestContext +public class OperatorsIndexPageTests : BaseTest { - private readonly Mock _mockMediator; - private readonly Mock _mockPermissionKeyProvider; - private readonly Mock _mockNavigationManager; - - public OperatorsIndexPageTests() - { - _mockMediator = new Mock(); - _mockPermissionKeyProvider = new Mock(); - _mockNavigationManager = new Mock(); - - _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); - - Services.AddSingleton(_mockMediator.Object); - Services.AddSingleton(_mockPermissionKeyProvider.Object); - Services.AddSingleton(_mockNavigationManager.Object); - - // Add required permission components - ComponentFactories.AddStub(); - } - [Fact] public void OperatorsIndex_RendersCorrectly() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsViewPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsViewPageTests.cs index 2a9225bb..3d98e035 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsViewPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsViewPageTests.cs @@ -1,6 +1,7 @@ using Bunit; using EstateManagementUI.BlazorServer.Components.Pages.Operators; using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using EstateManagementUI.BusinessLogic.Requests; using MediatR; using Microsoft.AspNetCore.Components; @@ -12,20 +13,8 @@ namespace EstateManagementUI.BlazorServer.Tests.Pages.Operators; -public class OperatorsViewPageTests : TestContext +public class OperatorsViewPageTests : BaseTest { - private readonly Mock _mockMediator; - private readonly Mock _mockNavigationManager; - - public OperatorsViewPageTests() - { - _mockMediator = new Mock(); - _mockNavigationManager = new Mock(); - - Services.AddSingleton(_mockMediator.Object); - Services.AddSingleton(_mockNavigationManager.Object); - } - [Fact] public void OperatorsView_RendersCorrectly() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Permissions/PermissionsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Permissions/PermissionsIndexPageTests.cs index 3d4d83f7..889db84d 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Permissions/PermissionsIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Permissions/PermissionsIndexPageTests.cs @@ -1,6 +1,7 @@ using Bunit; using EstateManagementUI.BlazorServer.Components.Permissions; using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using MediatR; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; @@ -10,28 +11,8 @@ namespace EstateManagementUI.BlazorServer.Tests.Pages.Permissions; -public class PermissionsIndexPageTests : TestContext +public class PermissionsIndexPageTests : BaseTest { - private readonly Mock _mockPermissionStore; - private readonly Mock _mockNavigationManager; - private readonly Mock _mockPermissionKeyProvider; - - public PermissionsIndexPageTests() - { - _mockPermissionStore = new Mock(); - _mockNavigationManager = new Mock(); - _mockPermissionKeyProvider = new Mock(); - - _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); - - Services.AddSingleton(_mockPermissionStore.Object); - Services.AddSingleton(_mockNavigationManager.Object); - Services.AddSingleton(_mockPermissionKeyProvider.Object); - - ComponentFactories.AddStub(); - ComponentFactories.AddStub(); - } - [Fact] public void PermissionsIndex_RendersCorrectly() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Reporting/ReportingIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Reporting/ReportingIndexPageTests.cs index 6b1a15ea..e5aeb0f1 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Reporting/ReportingIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Reporting/ReportingIndexPageTests.cs @@ -1,6 +1,7 @@ using Bunit; using EstateManagementUI.BlazorServer.Components.Permissions; using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; using Microsoft.Extensions.DependencyInjection; using Moq; using Shouldly; @@ -8,20 +9,8 @@ namespace EstateManagementUI.BlazorServer.Tests.Pages.Reporting; -public class ReportingIndexPageTests : TestContext +public class ReportingIndexPageTests : BaseTest { - private readonly Mock _mockPermissionKeyProvider; - - public ReportingIndexPageTests() - { - _mockPermissionKeyProvider = new Mock(); - _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); - - Services.AddSingleton(_mockPermissionKeyProvider.Object); - - ComponentFactories.AddStub(); - } - [Fact] public void ReportingIndex_RendersCorrectly() { diff --git a/EstateManagementUI.BlazorServer/Common/Helpers.cs b/EstateManagementUI.BlazorServer/Common/Helpers.cs new file mode 100644 index 00000000..bda45aef --- /dev/null +++ b/EstateManagementUI.BlazorServer/Common/Helpers.cs @@ -0,0 +1,165 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using Shared.General; +using Shared.Results; +using SimpleResults; +using System.Security.Claims; +using EstateManagementUI.BlazorServer.Models; + +namespace EstateManagementUI.BlazorServer.Common +{ + public static class Helpers + { + public static string GetComparisonLabel(List comparisonDates, string selectedComparisonDate) + { + if (comparisonDates == null) return "Comparison"; + if (!DateTime.TryParse(selectedComparisonDate, out var date)) + return "Comparison"; + ComparisonDateModel? comparisonDate = comparisonDates.FirstOrDefault(d => d.Date.Date == date.Date); + return comparisonDate?.Description ?? date.ToString("MMM dd"); + } + + public static Result GetEstateIdFromClaims(this AuthenticationState authState) + { + ClaimsPrincipal user = authState.User; + Result? estateIdClaim = ClaimsHelper.GetUserClaim(user, "estateId"); + if (estateIdClaim.IsFailed) + return ResultHelpers.CreateFailure(estateIdClaim); + Guid estateId = Guid.Parse(estateIdClaim.Data.Value); + return Result.Success(estateId); + } + + private static decimal GetSalesVariance(TodaysSalesModel todaysSales) + { + if (todaysSales == null) return 0; + if (todaysSales.ComparisonSalesValue == 0) + { + // If comparison is 0 and today is 0, no change + if (todaysSales.TodaysSalesValue == 0) return 0; + // If comparison is 0 but today has sales, treat as maximum positive change + return todaysSales.TodaysSalesValue > 0 ? 999m : 0; + } + + return (todaysSales.TodaysSalesValue - todaysSales.ComparisonSalesValue) / todaysSales.ComparisonSalesValue; + } + + public static string GetSalesBackgroundClass(TodaysSalesModel todaysSales) + { + Decimal variance = GetSalesVariance(todaysSales); + + return variance switch + { + _ when variance < 0 => "bg-red-50", // Worse + _ when variance == 0 => "bg-blue-50", // Same + _ when variance > 0 && variance < 0.2m => "bg-yellow-50", // Slightly better + _ => "bg-green-50", // Much better + }; + } + + public static string GetSalesTextClass(TodaysSalesModel todaysSales, double opacity = 1.0) + { + var variance = GetSalesVariance(todaysSales); + + return opacity switch + { + _ when opacity < 1.0 && variance < 0 => "text-red-700", + _ when opacity < 1.0 && variance == 0 => "text-blue-700", + _ when opacity < 1.0 && variance > 0 && variance < 0.2m => "text-yellow-700", + _ when opacity < 1.0 => "text-green-700", + _ when variance < 0 => "text-red-900", + _ when variance == 0 => "text-blue-900", + _ when variance > 0 && variance < 0.2m => "text-yellow-900", + _ => "text-green-900", + }; + } + + public static string GetSalesBorderClass(TodaysSalesModel todaysSales) + { + Decimal variance = GetSalesVariance(todaysSales); + return variance switch + { + _ when variance < 0 => "border-red-200", + _ when variance == 0 => "border-blue-200", + _ when variance > 0 && variance < 0.2m => "border-yellow-200", + _ => "border-green-200", + }; + } + + public static string GetSalesVarianceDisplay(TodaysSalesModel todaysSales) + { + Decimal variance = GetSalesVariance(todaysSales); + // Special case: comparison was 0, now has sales + if (variance >= 999m) return "NEW"; + Decimal percentageChange = variance * 100; + String sign = variance > 0 ? "+" : ""; + return $"{sign}{percentageChange:F1}%"; + } + + public static string GetFailedSalesBackgroundClass(TodaysSalesModel todaysSales) + { + Decimal variance = GetSalesVariance(todaysSales); + return variance switch + { + _ when variance < 0 => "bg-green-50", // Good - fewer failures + _ when variance == 0 => "bg-blue-50", // Same + _ when variance > 0 && variance < 0.2m => "bg-yellow-50", // Slightly worse + _ => "bg-red-50", // Much worse + }; + } + + public static string GetFailedSalesTextClass(TodaysSalesModel todaysSales, double opacity = 1.0) + { + Decimal variance = GetSalesVariance(todaysSales); + + return opacity switch + { + _ when opacity < 1.0 && variance < 0 => "text-green-700", + _ when opacity < 1.0 && variance == 0 => "text-blue-700", + _ when opacity < 1.0 && variance > 0 && variance < 0.2m => "text-yellow-700", + _ when opacity < 1.0 => "text-red-700", + _ when variance < 0 => "text-green-900", + _ when variance == 0 => "text-blue-900", + _ when variance > 0 && variance < 0.2m => "text-yellow-900", + _ => "text-red-900", + }; + } + + public static string GetFailedSalesBorderClass(TodaysSalesModel todaysSales) + { + Decimal variance = GetSalesVariance(todaysSales); + return variance switch + { + _ when variance < 0 => "border-green-200", + _ when variance == 0 => "border-blue-200", + _ when variance > 0 && variance < 0.2m => "border-yellow-200", + _ => "border-red-200", + }; + } + + public static string GetFailedSalesVarianceDisplay(TodaysSalesModel todaysSales) + { + Decimal variance = GetSalesVariance(todaysSales); + // Special case: comparison was 0, now has failures + if (variance >= 999m) return "NEW"; + Decimal percentageChange = variance * 100; + String sign = variance > 0 ? "+" : ""; + return $"{sign}{percentageChange:F1}%"; + } + + public static void NavigateToErrorPage(this NavigationManager navigationManager) + { + navigationManager.NavigateTo("/error", replace: true); + } + public static void NavigateToEntryPage(this NavigationManager navigationManager) + { + navigationManager.NavigateTo("/entry", replace: true); + } + + public static string GetTabClass(string activeTab, string tab) + { + return activeTab == tab + ? "border-blue-600 text-blue-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"; + } + } +} diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor index 0f59ca4c..04182246 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor @@ -1,5 +1,6 @@ @page "/contracts/{ContractId:guid}/edit" @rendermode InteractiveServer +@inherits AuthorizedComponentBase @using System.ComponentModel.DataAnnotations @using EstateManagementUI.BlazorServer.Factories @using EstateManagementUI.BlazorServer.Permissions @@ -416,11 +417,8 @@ else protected override async Task OnInitializedAsync() { - hasPermission = await PermissionService.HasPermissionAsync(PermissionSection.Contract, PermissionFunction.Edit); - if (hasPermission) - { - await LoadContract(); - } + await RequirePermission(PermissionSection.Contract, PermissionFunction.Edit); + await LoadContract(); } private async Task LoadContract() diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Index.razor b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Index.razor index 925e2937..1046b911 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Index.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Index.razor @@ -1,6 +1,7 @@ @page "/contracts" @using EstateManagementUI.BlazorServer.Factories @rendermode InteractiveServer +@inherits AuthorizedComponentBase @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests @inject IMediator Mediator @@ -96,6 +97,8 @@ { try { + await RequirePermission(PermissionSection.Contract, PermissionFunction.List); + var result = await Mediator.Send(new Queries.GetContractsQuery(CorrelationIdHelper.New(), "stubbed-token", Guid.Parse("11111111-1111-1111-1111-111111111111"))); if (result.IsSuccess) { contracts = ModelFactory.ConvertFrom(result.Data); diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/New.razor b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/New.razor index bbe26deb..7ffd28e6 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/New.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/New.razor @@ -1,5 +1,6 @@ @page "/contracts/new" @rendermode InteractiveServer +@inherits AuthorizedComponentBase @using System.ComponentModel.DataAnnotations @using EstateManagementUI.BlazorServer.Factories @using EstateManagementUI.BlazorServer.Permissions @@ -126,7 +127,7 @@ else protected override async Task OnInitializedAsync() { - hasPermission = await PermissionService.HasPermissionAsync(PermissionSection.Contract, PermissionFunction.Create); + await RequirePermission(PermissionSection.Contract, PermissionFunction.Create); await LoadOperators(); } diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/View.razor b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/View.razor index 0e9ab3c9..8e7a3765 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/View.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/View.razor @@ -2,6 +2,7 @@ @using EstateManagementUI.BlazorServer.Factories @using EstateManagementUI.BusinessLogic.Requests @rendermode InteractiveServer +@inherits AuthorizedComponentBase @inject IMediator Mediator @inject NavigationManager NavigationManager @@ -145,6 +146,7 @@ protected override async Task OnInitializedAsync() { + await RequirePermission(PermissionSection.Contract, PermissionFunction.View); await LoadContract(); } diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor b/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor index f398e493..a8249de9 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor @@ -1,8 +1,11 @@ @page "/estate" -@using EstateManagementUI.BlazorServer.Factories -@using EstateManagementUI.BusinessLogic.Requests +@using EstateManagementUI.BlazorServer.Common +@inherits AuthorizedComponentBase @rendermode InteractiveServer +@inject AuthenticationStateProvider AuthenticationStateProvider +@inject NavigationManager NavigationManager @inject IMediator Mediator +@inject IPermissionService PermissionService Estate Management @@ -24,10 +27,10 @@
@@ -258,167 +261,3 @@
}
- -@code { - private bool isLoading = true; - private EstateModel? estate; - private List? merchants; - private List? availableOperators; - private List assignedOperators = new(); - private List? contracts; - private string activeTab = "overview"; - private bool showAddOperator = false; - private string? selectedOperatorId; - private string? successMessage; - private string? errorMessage; - - protected override async Task OnInitializedAsync() - { - try - { - // Use stubbed mediator to get data - var correlationId = new CorrelationId(Guid.NewGuid()); - var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); - var accessToken = "stubbed-token"; - - var estateResult = await Mediator.Send(new Queries.GetEstateQuery(correlationId, accessToken, estateId)); - if (estateResult.IsSuccess) - { - estate = ModelFactory.ConvertFrom(estateResult.Data); - // Load assigned operators from estate model - if (estate.Operators != null) - { - assignedOperators = estate.Operators.Select(o => new OperatorModel - { - OperatorId = o.OperatorId, - Name = o.Name, - RequireCustomMerchantNumber = o.RequireCustomMerchantNumber, - RequireCustomTerminalNumber = o.RequireCustomTerminalNumber - }).ToList(); - } - } - - var merchantsResult = await Mediator.Send(new Queries.GetMerchantsQuery(correlationId, estateId)); - if (merchantsResult.IsSuccess) - { - merchants = ModelFactory.ConvertFrom(merchantsResult.Data); - } - - var operatorsResult = await Mediator.Send(new Queries.GetOperatorsQuery(correlationId, accessToken, estateId)); - if (operatorsResult.IsSuccess) - { - availableOperators = ModelFactory.ConvertFrom(operatorsResult.Data); - } - - var contractsResult = await Mediator.Send(new Queries.GetContractsQuery(correlationId, accessToken, estateId)); - if (contractsResult.IsSuccess) - { - contracts = ModelFactory.ConvertFrom(contractsResult.Data); - } - } - finally - { - isLoading = false; - } - } - - private string GetTabClass(string tab) - { - return activeTab == tab - ? "border-blue-600 text-blue-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"; - } - - private void SetActiveTab(string tab) - { - activeTab = tab; - ClearMessages(); - } - - private void ClearMessages() - { - successMessage = null; - errorMessage = null; - } - - private async Task AddOperatorToEstate() - { - if (string.IsNullOrEmpty(selectedOperatorId)) return; - - ClearMessages(); - - try - { - var correlationId = new CorrelationId(Guid.NewGuid()); - var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); - var accessToken = "stubbed-token"; - var operatorId = Guid.Parse(selectedOperatorId); - - var command = new Commands.AddOperatorToEstateCommand( - correlationId, - accessToken, - estateId, - operatorId - ); - - var result = await Mediator.Send(command); - - if (result.IsSuccess) - { - successMessage = "Operator added successfully"; - selectedOperatorId = null; - showAddOperator = false; - - // Add to assigned list - var op = availableOperators?.FirstOrDefault(o => o.OperatorId == operatorId); - if (op != null && !assignedOperators.Any(a => a.OperatorId == operatorId)) - { - assignedOperators.Add(op); - } - } - else - { - errorMessage = result.Message ?? "Failed to add operator"; - } - } - catch (Exception ex) - { - errorMessage = $"An error occurred: {ex.Message}"; - } - } - - private async Task RemoveOperatorFromEstate(Guid operatorId) - { - ClearMessages(); - - try - { - var correlationId = new CorrelationId(Guid.NewGuid()); - var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); - var accessToken = "stubbed-token"; - - var command = new Commands.RemoveOperatorFromEstateCommand( - correlationId, - accessToken, - estateId, - operatorId - ); - - var result = await Mediator.Send(command); - - if (result.IsSuccess) - { - successMessage = "Operator removed successfully"; - assignedOperators.RemoveAll(o => o.OperatorId == operatorId); - } - else - { - errorMessage = result.Message ?? "Failed to remove operator"; - } - } - catch (Exception ex) - { - errorMessage = $"An error occurred: {ex.Message}"; - } - } -} diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor.cs new file mode 100644 index 00000000..f6933b89 --- /dev/null +++ b/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor.cs @@ -0,0 +1,175 @@ +using EstateManagementUI.BlazorServer.Common; +using EstateManagementUI.BlazorServer.Factories; +using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BusinessLogic.Requests; +using Microsoft.AspNetCore.Components.Authorization; +using SimpleResults; + +namespace EstateManagementUI.BlazorServer.Components.Pages.Estate +{ + public partial class Index + { + private bool isLoading = true; + private EstateModel? estate; + private List? merchants; + private List? availableOperators; + private List assignedOperators = new(); + private List? contracts; + private string activeTab = "overview"; + private bool showAddOperator = false; + private string? selectedOperatorId; + private string? successMessage; + private string? errorMessage; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + { + await base.OnAfterRenderAsync(firstRender); + return; + } + try { + await base.OnInitializedAsync(); + + await RequirePermission(PermissionSection.Estate, PermissionFunction.View); + CorrelationId correlationId = new(Guid.NewGuid()); + Guid estateId = await this.GetEstateId(); + + Result estateResult = await Mediator.Send(new Queries.GetEstateQuery(correlationId, estateId)); + if (estateResult.IsSuccess) + { + estate = ModelFactory.ConvertFrom(estateResult.Data); + // Load assigned operators from estate model + if (estate.Operators != null) + { + assignedOperators = estate.Operators.Select(o => new OperatorModel + { + OperatorId = o.OperatorId, + Name = o.Name, + RequireCustomMerchantNumber = o.RequireCustomMerchantNumber, + RequireCustomTerminalNumber = o.RequireCustomTerminalNumber + }).ToList(); + } + } + + // TODO: Make these calls concurrent... + var merchantsResult = await Mediator.Send(new Queries.GetMerchantsQuery(correlationId, estateId)); + if (merchantsResult.IsSuccess) + { + merchants = ModelFactory.ConvertFrom(merchantsResult.Data); + } + + var operatorsResult = await Mediator.Send(new Queries.GetOperatorsQuery(correlationId, String.Empty, estateId)); + if (operatorsResult.IsSuccess) + { + availableOperators = ModelFactory.ConvertFrom(operatorsResult.Data); + } + + var contractsResult = await Mediator.Send(new Queries.GetContractsQuery(correlationId, String.Empty, estateId)); + if (contractsResult.IsSuccess) + { + contracts = ModelFactory.ConvertFrom(contractsResult.Data); + } + } + finally + { + isLoading = false; + this.StateHasChanged(); + } + } + + private void SetActiveTab(string tab) + { + activeTab = tab; + ClearMessages(); + } + + private void ClearMessages() + { + successMessage = null; + errorMessage = null; + } + + private async Task AddOperatorToEstate() + { + if (string.IsNullOrEmpty(selectedOperatorId)) return; + + ClearMessages(); + + try + { + var correlationId = new CorrelationId(Guid.NewGuid()); + var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); + var accessToken = "stubbed-token"; + var operatorId = Guid.Parse(selectedOperatorId); + + var command = new Commands.AddOperatorToEstateCommand( + correlationId, + accessToken, + estateId, + operatorId + ); + + var result = await Mediator.Send(command); + + if (result.IsSuccess) + { + successMessage = "Operator added successfully"; + selectedOperatorId = null; + showAddOperator = false; + + // Add to assigned list + var op = availableOperators?.FirstOrDefault(o => o.OperatorId == operatorId); + if (op != null && !assignedOperators.Any(a => a.OperatorId == operatorId)) + { + assignedOperators.Add(op); + } + } + else + { + errorMessage = result.Message ?? "Failed to add operator"; + } + } + catch (Exception ex) + { + errorMessage = $"An error occurred: {ex.Message}"; + } + } + + private async Task RemoveOperatorFromEstate(Guid operatorId) + { + ClearMessages(); + + try + { + var correlationId = new CorrelationId(Guid.NewGuid()); + var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); + var accessToken = "stubbed-token"; + + var command = new Commands.RemoveOperatorFromEstateCommand( + correlationId, + accessToken, + estateId, + operatorId + ); + + var result = await Mediator.Send(command); + + if (result.IsSuccess) + { + successMessage = "Operator removed successfully"; + assignedOperators.RemoveAll(o => o.OperatorId == operatorId); + } + else + { + errorMessage = result.Message ?? "Failed to remove operator"; + } + } + catch (Exception ex) + { + errorMessage = $"An error occurred: {ex.Message}"; + } + } + } +} diff --git a/EstateManagementUI.BlazorServer/Components/Pages/FileProcessing/Index.razor b/EstateManagementUI.BlazorServer/Components/Pages/FileProcessing/Index.razor index a0636564..4e4168fa 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/FileProcessing/Index.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/FileProcessing/Index.razor @@ -3,6 +3,7 @@ @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager @inject IPermissionKeyProvider PermissionKeyProvider @@ -131,6 +132,8 @@ { try { + await RequirePermission(PermissionSection.FileProcessing, PermissionFunction.List); + var correlationId = new CorrelationId(Guid.NewGuid()); var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); var merchantId = Guid.Parse("22222222-2222-2222-2222-222222222222"); diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Home.razor b/EstateManagementUI.BlazorServer/Components/Pages/Home.razor index c8ea7fb7..00a4f067 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Home.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Home.razor @@ -1,4 +1,5 @@ @page "/" +@using EstateManagementUI.BlazorServer.Common @rendermode InteractiveServer @inject IMediator Mediator @inject AuthenticationStateProvider AuthenticationStateProvider diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Home.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Home.razor.cs index 2cd9a6e1..e9598d48 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Home.razor.cs +++ b/EstateManagementUI.BlazorServer/Components/Pages/Home.razor.cs @@ -1,7 +1,7 @@ using System.Security.Claims; +using EstateManagementUI.BlazorServer.Common; using EstateManagementUI.BlazorServer.Factories; using EstateManagementUI.BlazorServer.Models; -using EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects; using EstateManagementUI.BusinessLogic.Requests; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; @@ -11,359 +11,216 @@ using Shared.Results; using SimpleResults; -namespace EstateManagementUI.BlazorServer.Components.Pages +namespace EstateManagementUI.BlazorServer.Components.Pages; + +public partial class Home { - public partial class Home + // Response code for low credit failures + private const string LOW_CREDIT_RESPONSE_CODE = "1009"; + + private bool isLoading = true; + private bool isAdministrator; + private string? errorMessage; + + private MerchantKpiModel? merchantKpi; + private TodaysSalesModel? todaysSales; + private TodaysSalesModel? todaysFailedSales; + private List? comparisonDates; + private List? recentMerchants; + private string _selectedComparisonDate = DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd"); + private int changeEventCounter = 0; + + protected override async Task OnInitializedAsync() { - // Response code for low credit failures - private const string LOW_CREDIT_RESPONSE_CODE = "1009"; - - private bool isLoading = true; - private bool isAdministrator; - private string? errorMessage; - - private MerchantKpiModel? merchantKpi; - private TodaysSalesModel? todaysSales; - private TodaysSalesModel? todaysFailedSales; - private List? comparisonDates; - private List? recentMerchants; - private string _selectedComparisonDate = DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd"); - private int changeEventCounter = 0; - - protected override async Task OnInitializedAsync() - { - // Keep prerender work minimal. This will still run during prerender, - // so avoid doing heavy/interactive-only tasks here. - await LogToConsole("OnInitializedAsync (prerender/early) START"); - // Do not call LoadDashboardData() here to avoid double-load when prerendering. - await base.OnInitializedAsync(); + // Keep prerender work minimal. This will still run during prerender, + // so avoid doing heavy/interactive-only tasks here. + await this.LogToConsole("OnInitializedAsync (prerender/early) START"); + // Do not call LoadDashboardData() here to avoid double-load when prerendering. + await base.OnInitializedAsync(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) { + await base.OnAfterRenderAsync(firstRender); + return; } - protected override async Task OnAfterRenderAsync(bool firstRender) + // This runs once after the interactive circuit is established. + await this.LogToConsole("OnAfterRenderAsync FIRST RENDER (interactive) - performing interactive initialization"); + + try { - if (!firstRender) { - await base.OnAfterRenderAsync(firstRender); + AuthenticationState authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + ClaimsPrincipal user = authState.User; + + // Redirect unauthenticated users to entry screen + if (!user.Identity?.IsAuthenticated ?? true) { + NavigationManager.NavigateToEntryPage(); return; } - // This runs once after the interactive circuit is established. - await LogToConsole("OnAfterRenderAsync FIRST RENDER (interactive) - performing interactive initialization"); - - try - { - AuthenticationState authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - ClaimsPrincipal user = authState.User; - - // Redirect unauthenticated users to entry screen - if (!user.Identity?.IsAuthenticated ?? true) { - NavigationManager.NavigateToEntryPage(); - return; - } + // Determine role and admin flag now that we're interactive + var role = await PermissionService.GetUserRoleAsync(); + this.isAdministrator = role == "Administrator"; + await this.LogToConsole($"User role: {role}, isAdministrator: {this.isAdministrator}"); - // Determine role and admin flag now that we're interactive - var role = await PermissionService.GetUserRoleAsync(); - isAdministrator = role == "Administrator"; - await LogToConsole($"User role: {role}, isAdministrator: {isAdministrator}"); + CorrelationId correlationId = new CorrelationId(Guid.NewGuid()); + var estateIdResult = authState.GetEstateIdFromClaims(); + if (estateIdResult.IsFailed) { + this.NavigationManager.NavigateToErrorPage(); + return; + } - CorrelationId correlationId = new CorrelationId(Guid.NewGuid()); - var estateIdResult = authState.GetEstateIdFromClaims(); - if (estateIdResult.IsFailed) { + // Only load dashboard data for non-admins + if (this.isAdministrator == false) { + var result = await this.LoadDashboardData(correlationId, estateIdResult.Data); + if (result.IsFailed) { this.NavigationManager.NavigateToErrorPage(); return; } - - // Only load dashboard data for non-admins - if (isAdministrator == false) { - var result = await LoadDashboardData(correlationId, estateIdResult.Data); - if (result.IsFailed) { - this.NavigationManager.NavigateToErrorPage(); - return; - } - } - else { - isLoading = false; - StateHasChanged(); - } } - catch (Exception ex) - { - errorMessage = $"Initialization error: {ex.Message}"; - isLoading = false; - StateHasChanged(); - this.NavigationManager.NavigateToErrorPage(); - return; + else { + this.isLoading = false; + this.StateHasChanged(); } - - await base.OnAfterRenderAsync(firstRender); } - - private async Task LoadDashboardData(CorrelationId correlationId, Guid estateId) + catch (Exception ex) { - await LogToConsole($"LoadDashboardData START - selectedDate: {_selectedComparisonDate}"); - try { - isLoading = true; - errorMessage = null; - StateHasChanged(); + this.errorMessage = $"Initialization error: {ex.Message}"; + this.isLoading = false; + this.StateHasChanged(); + this.NavigationManager.NavigateToErrorPage(); + return; + } + + await base.OnAfterRenderAsync(firstRender); + } + + private async Task LoadDashboardData(CorrelationId correlationId, Guid estateId) + { + await this.LogToConsole($"LoadDashboardData START - selectedDate: {this._selectedComparisonDate}"); + try { + this.isLoading = true; + this.errorMessage = null; + this.StateHasChanged(); - // Load comparison dates first (only if not already loaded) - Result comparisonDateResult = await LoadComparisonDates(correlationId, estateId); - if (comparisonDateResult.IsFailed) { - return ResultHelpers.CreateFailure(comparisonDateResult); - } + // Load comparison dates first (only if not already loaded) + Result comparisonDateResult = await this.LoadComparisonDates(correlationId, estateId); + if (comparisonDateResult.IsFailed) { + return ResultHelpers.CreateFailure(comparisonDateResult); + } - // Load all dashboard data in parallel - var kpiTask = Mediator.Send(new Queries.GetMerchantKpiQuery(correlationId, estateId)); - var salesTask = Mediator.Send(new Queries.GetTodaysSalesQuery(correlationId, estateId, comparisonDateResult.Data)); - var failedSalesTask = Mediator.Send(new Queries.GetTodaysFailedSalesQuery(correlationId, estateId, LOW_CREDIT_RESPONSE_CODE, comparisonDateResult.Data)); - var merchantsTask = Mediator.Send(new Queries.GetRecentMerchantsQuery(correlationId, estateId)); + // Load all dashboard data in parallel + var kpiTask = Mediator.Send(new Queries.GetMerchantKpiQuery(correlationId, estateId)); + var salesTask = Mediator.Send(new Queries.GetTodaysSalesQuery(correlationId, estateId, comparisonDateResult.Data)); + var failedSalesTask = Mediator.Send(new Queries.GetTodaysFailedSalesQuery(correlationId, estateId, LOW_CREDIT_RESPONSE_CODE, comparisonDateResult.Data)); + var merchantsTask = Mediator.Send(new Queries.GetRecentMerchantsQuery(correlationId, estateId)); - await Task.WhenAll(kpiTask, salesTask, failedSalesTask, merchantsTask); + await Task.WhenAll(kpiTask, salesTask, failedSalesTask, merchantsTask); - // Process results - if (kpiTask.Result.IsFailed) - return ResultHelpers.CreateFailure(kpiTask.Result); + // Process results + if (kpiTask.Result.IsFailed) + return ResultHelpers.CreateFailure(kpiTask.Result); - merchantKpi = ModelFactory.ConvertFrom(kpiTask.Result.Data); + this.merchantKpi = ModelFactory.ConvertFrom((BusinessLogic.Models.MerchantKpiModel)kpiTask.Result.Data); - if (salesTask.Result.IsFailed) - return ResultHelpers.CreateFailure(salesTask.Result); + if (salesTask.Result.IsFailed) + return ResultHelpers.CreateFailure(salesTask.Result); - todaysSales = ModelFactory.ConvertFrom(salesTask.Result.Data); + this.todaysSales = ModelFactory.ConvertFrom((BusinessLogic.Models.TodaysSalesModel)salesTask.Result.Data); - if (failedSalesTask.Result.IsFailed) - return ResultHelpers.CreateFailure(failedSalesTask.Result); + if (failedSalesTask.Result.IsFailed) + return ResultHelpers.CreateFailure(failedSalesTask.Result); - todaysFailedSales = ModelFactory.ConvertFrom(failedSalesTask.Result.Data); + this.todaysFailedSales = ModelFactory.ConvertFrom((BusinessLogic.Models.TodaysSalesModel)failedSalesTask.Result.Data); - if (merchantsTask.Result.IsFailed) - return ResultHelpers.CreateFailure(merchantsTask.Result); + if (merchantsTask.Result.IsFailed) + return ResultHelpers.CreateFailure>(merchantsTask.Result); - // Note: API returns merchants in creation order (newest first) - // If ordering is incorrect, would need CreatedDate field in the model - recentMerchants = ModelFactory.ConvertFrom(merchantsTask.Result.Data); + // Note: API returns merchants in creation order (newest first) + // If ordering is incorrect, would need CreatedDate field in the model + this.recentMerchants = ModelFactory.ConvertFrom((List)merchantsTask.Result.Data); - return Result.Success(); - } - catch (Exception ex) { - errorMessage = $"Failed to load dashboard data: {ex.Message}"; - return Result.Failure(ex.GetCombinedExceptionMessages()); - } - finally { - isLoading = false; - StateHasChanged(); - await LogToConsole("LoadDashboardData END"); - } + return Result.Success(); } - - private async Task> LoadComparisonDates(CorrelationId correlationId, Guid estateId) { - - Result> comparisonDatesResult; - if (comparisonDates == null || !comparisonDates.Any()) { - comparisonDatesResult = await Mediator.Send(new Queries.GetComparisonDatesQuery(correlationId, estateId)); - if (comparisonDatesResult.IsFailed) { - return ResultHelpers.CreateFailure(comparisonDatesResult); - } - - - comparisonDates = ModelFactory.ConvertFrom(comparisonDatesResult.Data); - if (comparisonDates != null && comparisonDates.Any()) { - // Set default comparison date to the first one only on initial load - _selectedComparisonDate = comparisonDates.First().Date.ToString("yyyy-MM-dd"); - } - } - - if (!DateTime.TryParse(_selectedComparisonDate, out DateTime comparisonDate)) { - // Fallback to a week ago if parse fails - comparisonDate = DateTime.Now.AddDays(-7); - } - - return Result.Success(comparisonDate); + catch (Exception ex) { + this.errorMessage = $"Failed to load dashboard data: {ex.Message}"; + return Result.Failure(ex.GetCombinedExceptionMessages()); + } + finally { + this.isLoading = false; + this.StateHasChanged(); + await this.LogToConsole("LoadDashboardData END"); } + } - private async Task OnComparisonDateChanged() - { - changeEventCounter++; - await LogToConsole($"🔥 OnComparisonDateChanged FIRED! Count: {changeEventCounter}, New value: {_selectedComparisonDate}"); + private async Task> LoadComparisonDates(CorrelationId correlationId, Guid estateId) { - // This is called after _selectedComparisonDate is updated by @bind-Value - if (isAdministrator == false) { + Result> comparisonDatesResult; + if (this.comparisonDates == null || !this.comparisonDates.Any()) { + comparisonDatesResult = await Mediator.Send(new Queries.GetComparisonDatesQuery(correlationId, estateId)); + if (comparisonDatesResult.IsFailed) { + return ResultHelpers.CreateFailure(comparisonDatesResult); + } - CorrelationId correlationId = new CorrelationId(Guid.NewGuid()); - AuthenticationState authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - Result estateIdResult = authState.GetEstateIdFromClaims(); - if (estateIdResult.IsFailed) { - this.NavigationManager.NavigateToErrorPage(); - return; - } - await LogToConsole($"Loading dashboard data for date: {_selectedComparisonDate}"); - var loadResult = await LoadDashboardData(correlationId,estateIdResult.Data); - if (loadResult.IsFailed) { - this.NavigationManager.NavigateToErrorPage(); - return; - } - StateHasChanged(); - await LogToConsole("Dashboard data reload complete"); - } - else { - await LogToConsole("User is administrator - skipping data reload"); + this.comparisonDates = ModelFactory.ConvertFrom(comparisonDatesResult.Data); + if (this.comparisonDates != null && this.comparisonDates.Any()) { + // Set default comparison date to the first one only on initial load + this._selectedComparisonDate = this.comparisonDates.First().Date.ToString("yyyy-MM-dd"); } } - private async Task LogToConsole(string message) - { - try - { - await JSRuntime.InvokeVoidAsync("console.log", $"[Home.razor {DateTime.Now:HH:mm:ss.fff}] {message}"); - } - catch - { - // Ignore JS interop errors during prerendering - } + if (!DateTime.TryParse(this._selectedComparisonDate, out DateTime comparisonDate)) { + // Fallback to a week ago if parse fails + comparisonDate = DateTime.Now.AddDays(-7); } + + return Result.Success(comparisonDate); } -} + private async Task OnComparisonDateChanged() + { + this.changeEventCounter++; + await this.LogToConsole($"🔥 OnComparisonDateChanged FIRED! Count: {this.changeEventCounter}, New value: {this._selectedComparisonDate}"); + // This is called after _selectedComparisonDate is updated by @bind-Value + if (this.isAdministrator == false) { -public static class Helpers -{ - public static string GetComparisonLabel(List comparisonDates, string selectedComparisonDate) - { - if (comparisonDates == null) return "Comparison"; - if (!DateTime.TryParse(selectedComparisonDate, out var date)) - return "Comparison"; - ComparisonDateModel? comparisonDate = comparisonDates.FirstOrDefault(d => d.Date.Date == date.Date); - return comparisonDate?.Description ?? date.ToString("MMM dd"); - } + CorrelationId correlationId = new CorrelationId(Guid.NewGuid()); + AuthenticationState authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + Result estateIdResult = authState.GetEstateIdFromClaims(); + if (estateIdResult.IsFailed) { + this.NavigationManager.NavigateToErrorPage(); + return; + } - public static Result GetEstateIdFromClaims(this AuthenticationState authState) { - ClaimsPrincipal user = authState.User; - Result? estateIdClaim = ClaimsHelper.GetUserClaim(user, "estateId"); - if (estateIdClaim.IsFailed) - return ResultHelpers.CreateFailure(estateIdClaim); - Guid estateId = Guid.Parse(estateIdClaim.Data.Value); - return Result.Success(estateId); + await this.LogToConsole($"Loading dashboard data for date: {this._selectedComparisonDate}"); + var loadResult = await this.LoadDashboardData(correlationId,estateIdResult.Data); + if (loadResult.IsFailed) { + this.NavigationManager.NavigateToErrorPage(); + return; + } + this.StateHasChanged(); + await this.LogToConsole("Dashboard data reload complete"); + } + else { + await this.LogToConsole("User is administrator - skipping data reload"); + } } - private static decimal GetSalesVariance(TodaysSalesModel todaysSales) + private async Task LogToConsole(string message) { - if (todaysSales == null) return 0; - if (todaysSales.ComparisonSalesValue == 0) + try { - // If comparison is 0 and today is 0, no change - if (todaysSales.TodaysSalesValue == 0) return 0; - // If comparison is 0 but today has sales, treat as maximum positive change - return todaysSales.TodaysSalesValue > 0 ? 999m : 0; + await JSRuntimeExtensions.InvokeVoidAsync((IJSRuntime)this.JSRuntime, "console.log", $"[Home.razor {DateTime.Now:HH:mm:ss.fff}] {message}"); + } + catch + { + // Ignore JS interop errors during prerendering } - - return (todaysSales.TodaysSalesValue - todaysSales.ComparisonSalesValue) / todaysSales.ComparisonSalesValue; - } - - public static string GetSalesBackgroundClass(TodaysSalesModel todaysSales) - { - Decimal variance = GetSalesVariance(todaysSales); - - return variance switch { - _ when variance < 0 => "bg-red-50", // Worse - _ when variance == 0 => "bg-blue-50", // Same - _ when variance > 0 && variance < 0.2m => "bg-yellow-50", // Slightly better - _ => "bg-green-50", // Much better - }; - } - - public static string GetSalesTextClass(TodaysSalesModel todaysSales, double opacity = 1.0) - { - var variance = GetSalesVariance(todaysSales); - - return opacity switch { - _ when opacity < 1.0 && variance < 0 => "text-red-700", - _ when opacity < 1.0 && variance == 0 => "text-blue-700", - _ when opacity < 1.0 && variance > 0 && variance < 0.2m => "text-yellow-700", - _ when opacity < 1.0 => "text-green-700", - _ when variance < 0 => "text-red-900", - _ when variance == 0 => "text-blue-900", - _ when variance > 0 && variance < 0.2m => "text-yellow-900", - _ => "text-green-900", - }; - } - - public static string GetSalesBorderClass(TodaysSalesModel todaysSales) - { - Decimal variance = GetSalesVariance(todaysSales); - return variance switch { - _ when variance < 0 => "border-red-200", - _ when variance == 0 => "border-blue-200", - _ when variance > 0 && variance < 0.2m => "border-yellow-200", - _ => "border-green-200", - }; - } - - public static string GetSalesVarianceDisplay(TodaysSalesModel todaysSales) - { - Decimal variance = GetSalesVariance(todaysSales); - // Special case: comparison was 0, now has sales - if (variance >= 999m) return "NEW"; - Decimal percentageChange = variance * 100; - String sign = variance > 0 ? "+" : ""; - return $"{sign}{percentageChange:F1}%"; - } - - public static string GetFailedSalesBackgroundClass(TodaysSalesModel todaysSales) - { - Decimal variance = GetSalesVariance(todaysSales); - return variance switch { - _ when variance < 0 => "bg-green-50", // Good - fewer failures - _ when variance == 0 => "bg-blue-50", // Same - _ when variance > 0 && variance < 0.2m => "bg-yellow-50", // Slightly worse - _ => "bg-red-50", // Much worse - }; - } - - public static string GetFailedSalesTextClass(TodaysSalesModel todaysSales, double opacity = 1.0) - { - Decimal variance = GetSalesVariance(todaysSales); - - return opacity switch { - _ when opacity < 1.0 && variance < 0 => "text-green-700", - _ when opacity < 1.0 && variance == 0 => "text-blue-700", - _ when opacity < 1.0 && variance > 0 && variance < 0.2m => "text-yellow-700", - _ when opacity < 1.0 => "text-red-700", - _ when variance < 0 => "text-green-900", - _ when variance == 0 => "text-blue-900", - _ when variance > 0 && variance < 0.2m => "text-yellow-900", - _ => "text-red-900", - }; - } - - public static string GetFailedSalesBorderClass(TodaysSalesModel todaysSales) - { - Decimal variance = GetSalesVariance(todaysSales); - return variance switch { - _ when variance < 0 => "border-green-200", - _ when variance == 0 => "border-blue-200", - _ when variance > 0 && variance < 0.2m => "border-yellow-200", - _ => "border-red-200", - }; - } - - public static string GetFailedSalesVarianceDisplay(TodaysSalesModel todaysSales) - { - Decimal variance = GetSalesVariance(todaysSales); - // Special case: comparison was 0, now has failures - if (variance >= 999m) return "NEW"; - Decimal percentageChange = variance * 100; - String sign = variance > 0 ? "+" : ""; - return $"{sign}{percentageChange:F1}%"; } +} - public static void NavigateToErrorPage(this NavigationManager navigationManager) { - navigationManager.NavigateTo("/error", replace: true); - } - public static void NavigateToEntryPage(this NavigationManager navigationManager) - { - navigationManager.NavigateTo("/entry", replace: true); - } - -} \ No newline at end of file diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor index fe5a2142..204ec72e 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor @@ -4,6 +4,7 @@ @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager @inject IPermissionService PermissionService @@ -117,14 +118,8 @@ else protected override async Task OnInitializedAsync() { - // Check permission first - hasPermission = await PermissionService.HasPermissionAsync(PermissionSection.Merchant, PermissionFunction.MakeDeposit); + await RequirePermission(PermissionSection.Merchant, PermissionFunction.MakeDeposit); - if (!hasPermission) - { - return; - } - // Set default date to today model.Date = DateTime.Today; diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor index 60bb4986..80606710 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor @@ -4,6 +4,7 @@ @using EstateManagementUI.BlazorServer.Factories @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests +@inherits AuthorizedComponentBase @inject IMediator Mediator @inject NavigationManager NavigationManager @inject IPermissionService PermissionService @@ -469,15 +470,10 @@ else protected override async Task OnInitializedAsync() { - // Check permission first - hasPermission = await PermissionService.HasPermissionAsync(PermissionSection.Merchant, PermissionFunction.Edit); - - if (hasPermission) - { - await LoadMerchant(); - await LoadOperators(); - await LoadContracts(); - } + await RequirePermission(PermissionSection.Merchant, PermissionFunction.Edit); + await LoadMerchant(); + await LoadOperators(); + await LoadContracts(); } private async Task LoadMerchant() diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor index f50be497..c1e94d2e 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor @@ -4,6 +4,7 @@ @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager @inject IPermissionKeyProvider PermissionKeyProvider @@ -134,6 +135,8 @@ { try { + await RequirePermission(PermissionSection.Merchant, PermissionFunction.List); + var correlationId = new CorrelationId(Guid.NewGuid()); var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/New.razor b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/New.razor index 98abd2a8..61888054 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/New.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/New.razor @@ -4,32 +4,12 @@ @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager @inject IPermissionService PermissionService Create New Merchant -@if (!hasPermission) -{ -
-
-
- - - -
-

Access Denied

-

You don't have permission to create merchants.

-
-
- -
-
-} -else -{
@@ -193,12 +173,11 @@ else @code { private readonly New.CreateMerchantModel model = new(); private bool isSaving = false; - private bool hasPermission = false; private string? errorMessage; protected override async Task OnInitializedAsync() { - hasPermission = await PermissionService.HasPermissionAsync(PermissionSection.Merchant, PermissionFunction.Create); + await RequirePermission(PermissionSection.Merchant, PermissionFunction.Create); } private async Task HandleSubmit() @@ -326,4 +305,3 @@ else public string? PhoneNumber { get; set; } } } -} diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/View.razor b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/View.razor index 5aaa41d6..c4ca9e90 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/View.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/View.razor @@ -2,6 +2,7 @@ @using EstateManagementUI.BlazorServer.Factories @rendermode InteractiveServer @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager @using EstateManagementUI.BlazorServer.Models @using EstateManagementUI.BusinessLogic.Requests @@ -330,6 +331,7 @@ protected override async Task OnInitializedAsync() { + await RequirePermission(PermissionSection.Merchant, PermissionFunction.View); await LoadMerchant(); } diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Operators/Edit.razor b/EstateManagementUI.BlazorServer/Components/Pages/Operators/Edit.razor index 2df5e7a2..ff50e915 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Operators/Edit.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Operators/Edit.razor @@ -5,6 +5,7 @@ @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager @inject IPermissionService PermissionService @@ -146,11 +147,9 @@ else protected override async Task OnInitializedAsync() { - hasPermission = await PermissionService.HasPermissionAsync(PermissionSection.Operator, PermissionFunction.Edit); - if (hasPermission) - { - await LoadOperator(); - } + await RequirePermission(PermissionSection.Operator, PermissionFunction.Edit); + await LoadOperator(); + } private async Task LoadOperator() diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Operators/Index.razor b/EstateManagementUI.BlazorServer/Components/Pages/Operators/Index.razor index 4a4495bb..7f7883ea 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Operators/Index.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Operators/Index.razor @@ -4,6 +4,7 @@ @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager @inject IPermissionKeyProvider PermissionKeyProvider @@ -119,6 +120,8 @@ { try { + await RequirePermission(PermissionSection.Operator, PermissionFunction.List); + var correlationId = new CorrelationId(Guid.NewGuid()); var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); var accessToken = "stubbed-token"; diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Operators/New.razor b/EstateManagementUI.BlazorServer/Components/Pages/Operators/New.razor index 259b3f32..71223bc5 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Operators/New.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Operators/New.razor @@ -4,32 +4,12 @@ @using EstateManagementUI.BlazorServer.Permissions @using EstateManagementUI.BusinessLogic.Requests @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager @inject IPermissionService PermissionService Create New Operator -@if (!hasPermission) -{ -
-
-
- - - -
-

Access Denied

-

You don't have permission to create operators.

-
-
- -
-
-} -else -{
@@ -109,12 +89,11 @@ else @code { private CreateOperatorModel model = new(); private bool isSaving = false; - private bool hasPermission = false; private string? errorMessage; protected override async Task OnInitializedAsync() { - hasPermission = await PermissionService.HasPermissionAsync(PermissionSection.Operator, PermissionFunction.Create); + await RequirePermission(PermissionSection.Operator, PermissionFunction.Create); } private async Task HandleSubmit() @@ -174,4 +153,3 @@ else public bool RequireCustomTerminalNumber { get; set; } } } -} diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Operators/View.razor b/EstateManagementUI.BlazorServer/Components/Pages/Operators/View.razor index 0bed1c2a..5df4e361 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Operators/View.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Operators/View.razor @@ -3,6 +3,7 @@ @using EstateManagementUI.BusinessLogic.Requests @rendermode InteractiveServer @inject IMediator Mediator +@inherits AuthorizedComponentBase @inject NavigationManager NavigationManager View Operator @@ -86,6 +87,8 @@ protected override async Task OnInitializedAsync() { + await RequirePermission(PermissionSection.Operator, PermissionFunction.View); + await LoadOperator(); } diff --git a/EstateManagementUI.BlazorServer/Components/Permissions/AuthorizedComponentBase.cs b/EstateManagementUI.BlazorServer/Components/Permissions/AuthorizedComponentBase.cs new file mode 100644 index 00000000..dd90846b --- /dev/null +++ b/EstateManagementUI.BlazorServer/Components/Permissions/AuthorizedComponentBase.cs @@ -0,0 +1,44 @@ +using EstateManagementUI.BlazorServer.Common; +using EstateManagementUI.BlazorServer.Permissions; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using System.Security.Claims; +using SimpleResults; + +public abstract class AuthorizedComponentBase : ComponentBase +{ + [CascadingParameter] + private Task AuthenticationStateTask { get; set; } = default!; + + protected ClaimsPrincipal User { get; private set; } = default!; + protected AuthenticationState AuthState { get; private set; } = default!; + + protected override async Task OnInitializedAsync() { + + this.AuthState = await AuthenticationStateTask; + User = this.AuthState.User; + } + + [Inject] protected IPermissionService PermissionService { get; set; } = default!; + [Inject] protected NavigationManager NavigationManager { get; set; } = default!; + + protected async Task RequirePermission(PermissionSection permissionSection, PermissionFunction permissionFunction) + { + // Do a permission check here + Boolean hasPermission = await this.PermissionService.HasPermissionAsync(permissionSection, permissionFunction); + if (hasPermission == false) + { + // TODO: Navigate to access denied page + this.NavigationManager.NavigateToErrorPage(); + return; + } + } + + protected async Task GetEstateId() { + Result estateIdResult = this.AuthState.GetEstateIdFromClaims(); + if (estateIdResult.IsFailed) { + this.NavigationManager.NavigateToErrorPage(); + } + return estateIdResult.Data; + } +} \ No newline at end of file diff --git a/EstateManagementUI.BlazorServer/Permissions/PermissionFunction.cs b/EstateManagementUI.BlazorServer/Permissions/PermissionFunction.cs index 18fbbd72..178d60a6 100644 --- a/EstateManagementUI.BlazorServer/Permissions/PermissionFunction.cs +++ b/EstateManagementUI.BlazorServer/Permissions/PermissionFunction.cs @@ -9,5 +9,6 @@ public enum PermissionFunction Create, Edit, Delete, - MakeDeposit + MakeDeposit, + List } diff --git a/EstateManagementUI.BlazorServer/Program.cs b/EstateManagementUI.BlazorServer/Program.cs index 2a5ce307..27b5d09e 100644 --- a/EstateManagementUI.BlazorServer/Program.cs +++ b/EstateManagementUI.BlazorServer/Program.cs @@ -21,6 +21,7 @@ using ClientProxyBase; using SecurityService.Client; using Shared.General; +using TransactionProcessor.Client; var builder = WebApplication.CreateBuilder(args); @@ -237,6 +238,7 @@ builder.Services.RegisterHttpClient(); builder.Services.RegisterHttpClient(); + builder.Services.RegisterHttpClient(); } WebApplication app = builder.Build(); diff --git a/EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs b/EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs index 83df32fc..6f84cc3c 100644 --- a/EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs +++ b/EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs @@ -1,5 +1,6 @@ using EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects; using EstateManagementUI.BusinessLogic.Models; +using TransactionProcessor.DataTransferObjects.Responses.Estate; namespace EstateManagementUI.BusinessLogic.Client; @@ -66,4 +67,24 @@ public static List ConvertFrom(List apiResultDat return merchants; } + + public static EstateModel ConvertFrom(EstateResponse apiResultData) { + EstateModel model = new EstateModel { + Reference = apiResultData.EstateReference, + EstateId = apiResultData.EstateId, + EstateName = apiResultData.EstateName, + Operators = new List() + }; + + foreach (EstateOperatorResponse estateOperatorResponse in apiResultData.Operators) { + model.Operators.Add(new EstateOperatorModel { + Name = estateOperatorResponse.Name, + OperatorId = estateOperatorResponse.OperatorId, + RequireCustomMerchantNumber = estateOperatorResponse.RequireCustomMerchantNumber, + RequireCustomTerminalNumber = estateOperatorResponse.RequireCustomTerminalNumber, + }); + } + + return model; + } } \ No newline at end of file diff --git a/EstateManagmentUI.BusinessLogic/Client/DateMethods.cs b/EstateManagmentUI.BusinessLogic/Client/DateMethods.cs index 6837eaf1..eb79b5ad 100644 --- a/EstateManagmentUI.BusinessLogic/Client/DateMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/DateMethods.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; +using TransactionProcessor.Client; namespace EstateManagementUI.BusinessLogic.Client { @@ -21,10 +22,13 @@ Task>> GetComparisonDates(Queries.GetComparison public partial class ApiClient : IApiClient { private readonly IEstateReportingApiClient EstateReportingApiClient; private readonly ISecurityServiceClient SecurityServiceClient; + private readonly ITransactionProcessorClient TransactionProcessorClient; - public ApiClient(IEstateReportingApiClient estateReportingApiClient, ISecurityServiceClient securityServiceClient) { + public ApiClient(IEstateReportingApiClient estateReportingApiClient, ISecurityServiceClient securityServiceClient, + ITransactionProcessorClient transactionProcessorClient) { this.EstateReportingApiClient = estateReportingApiClient; this.SecurityServiceClient = securityServiceClient; + this.TransactionProcessorClient = transactionProcessorClient; } public async Task>> GetComparisonDates(Queries.GetComparisonDatesQuery request, CancellationToken cancellationToken) { diff --git a/EstateManagmentUI.BusinessLogic/Client/EstateMethods.cs b/EstateManagmentUI.BusinessLogic/Client/EstateMethods.cs new file mode 100644 index 00000000..03cb85cc --- /dev/null +++ b/EstateManagmentUI.BusinessLogic/Client/EstateMethods.cs @@ -0,0 +1,35 @@ +using EstateManagementUI.BusinessLogic.Models; +using EstateManagementUI.BusinessLogic.Requests; +using SimpleResults; +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Text; +using Shared.Results; +using TransactionProcessor.DataTransferObjects.Responses.Estate; + +namespace EstateManagementUI.BusinessLogic.Client { + public partial interface IApiClient { + Task> GetEstate(Queries.GetEstateQuery request, + CancellationToken cancellationToken); + } + + public partial class ApiClient : IApiClient { + + public async Task> GetEstate(Queries.GetEstateQuery request, + CancellationToken cancellationToken) { + // Get a token here + Result token = await this.GetToken(cancellationToken); + if (token.IsFailed) + return ResultHelpers.CreateFailure(token); + + Result? apiResult = await this.TransactionProcessorClient.GetEstate(token.Data, request.EstateId, cancellationToken); + if (apiResult.IsFailed) + return ResultHelpers.CreateFailure(apiResult); + + EstateModel estate = APIModelFactory.ConvertFrom(apiResult.Data); + + return Result.Success(estate); + } + } +} diff --git a/EstateManagmentUI.BusinessLogic/EstateManagementUI.BusinessLogic.csproj b/EstateManagmentUI.BusinessLogic/EstateManagementUI.BusinessLogic.csproj index 754c26b4..317bd052 100644 --- a/EstateManagmentUI.BusinessLogic/EstateManagementUI.BusinessLogic.csproj +++ b/EstateManagmentUI.BusinessLogic/EstateManagementUI.BusinessLogic.csproj @@ -11,5 +11,6 @@ + diff --git a/EstateManagmentUI.BusinessLogic/RequestHandlers/DateRequestHandler.cs b/EstateManagmentUI.BusinessLogic/RequestHandlers/DateRequestHandler.cs index 783de7ed..bb400575 100644 --- a/EstateManagmentUI.BusinessLogic/RequestHandlers/DateRequestHandler.cs +++ b/EstateManagmentUI.BusinessLogic/RequestHandlers/DateRequestHandler.cs @@ -31,7 +31,7 @@ public EstateRequestHandler(IApiClient apiClient) { public async Task> Handle(Queries.GetEstateQuery request, CancellationToken cancellationToken) { - return Result.Success(StubTestData.GetMockEstate()); + return await this.ApiClient.GetEstate(request, cancellationToken); } public async Task Handle(Commands.AddOperatorToEstateCommand request, diff --git a/EstateManagmentUI.BusinessLogic/Requests/Requests.cs b/EstateManagmentUI.BusinessLogic/Requests/Requests.cs index bf931650..f17452a0 100644 --- a/EstateManagmentUI.BusinessLogic/Requests/Requests.cs +++ b/EstateManagmentUI.BusinessLogic/Requests/Requests.cs @@ -13,7 +13,7 @@ public static class CorrelationIdHelper public static class Queries { - public record GetEstateQuery(CorrelationId CorrelationId, string AccessToken, Guid EstateId) : IRequest>; + public record GetEstateQuery(CorrelationId CorrelationId, Guid EstateId) : IRequest>; public record GetMerchantsQuery(CorrelationId CorrelationId, Guid EstateId) : IRequest>>; public record GetRecentMerchantsQuery(CorrelationId CorrelationId, Guid EstateId) : IRequest>>; public record GetOperatorsQuery(CorrelationId CorrelationId, string AccessToken, Guid EstateId) : IRequest>>;