diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 97e9c693..bc8cf9ea 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -1,192 +1,192 @@ -name: Build and Test Pull Requests +#name: Build and Test Pull Requests -on: - pull_request: - branches: - - main +# on: +# pull_request: +# branches: +# - main -jobs: - unittests: - name: "Build and Unit Test" +# jobs: +# unittests: +# name: "Build and Unit Test" - env: - ASPNETCORE_ENVIRONMENT: "Production" +# env: +# ASPNETCORE_ENVIRONMENT: "Production" - runs-on: ubuntu-latest +# runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2.3.4 +# steps: +# - uses: actions/checkout@v2.3.4 - - name: Restore Nuget Packages - run: dotnet restore EstateManagementUI.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} +# - name: Restore Nuget Packages +# run: dotnet restore EstateManagementUI.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} - - name: Build Code - run: | - dotnet build EstateManagementUI.sln --configuration Release +# - name: Build Code +# run: | +# dotnet build EstateManagementUI.sln --configuration Release - - name: Run Unit Tests - run: | - dotnet test "EstateManagementUI.BusinessLogic.Tests\EstateManagementUI.BusinessLogic.Tests.csproj" - dotnet test "EstateManagementUI.UITests\EstateManagementUI.UITests.csproj" +# - name: Run Unit Tests +# run: | +# dotnet test "EstateManagementUI.BusinessLogic.Tests\EstateManagementUI.BusinessLogic.Tests.csproj" +# dotnet test "EstateManagementUI.UITests\EstateManagementUI.UITests.csproj" - chrometests: - name: "Build and Test UI - Chrome" - env: - ASPNETCORE_ENVIRONMENT: "Production" - NODE_VERSION: '14.x' # set this to the node version to use +# chrometests: +# name: "Build and Test UI - Chrome" +# env: +# ASPNETCORE_ENVIRONMENT: "Production" +# NODE_VERSION: '14.x' # set this to the node version to use - runs-on: ubuntu-latest +# runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2.3.4 +# steps: +# - uses: actions/checkout@v2.3.4 - - name: Trust Certificate - run: | - sudo apt-get install expect - certPath="$GITHUB_WORKSPACE/Certificates/aspnetapp-root-cert.pfx" - password="password" +# - name: Trust Certificate +# run: | +# sudo apt-get install expect +# certPath="$GITHUB_WORKSPACE/Certificates/aspnetapp-root-cert.pfx" +# password="password" - # Use expect to automate the passphrase input - expect -c " - spawn openssl pkcs12 -in \"$certPath\" -clcerts -nokeys -out certificate.crt -password pass:\"$password\" - expect \"Enter Import Password:\" { send \"$password\n\" } - expect eof - " +# # Use expect to automate the passphrase input +# expect -c " +# spawn openssl pkcs12 -in \"$certPath\" -clcerts -nokeys -out certificate.crt -password pass:\"$password\" +# expect \"Enter Import Password:\" { send \"$password\n\" } +# expect eof +# " - expect -c " - spawn openssl pkcs12 -in \"$certPath\" -nocerts -out private.key -password pass:\"$password\" - expect \"Enter Import Password:\" { send \"$password\n\" } - expect eof - " +# expect -c " +# spawn openssl pkcs12 -in \"$certPath\" -nocerts -out private.key -password pass:\"$password\" +# expect \"Enter Import Password:\" { send \"$password\n\" } +# expect eof +# " - sudo cp certificate.crt /usr/local/share/ca-certificates/aspnetapp-root-cert.crt - sudo update-ca-certificates +# sudo cp certificate.crt /usr/local/share/ca-certificates/aspnetapp-root-cert.crt +# sudo update-ca-certificates - sudo cp private.key /etc/ssl/private/aspnetapp-root-cert.key - sudo chmod 400 /etc/ssl/private/aspnetapp-root-cert.key +# sudo cp private.key /etc/ssl/private/aspnetapp-root-cert.key +# sudo chmod 400 /etc/ssl/private/aspnetapp-root-cert.key - - name: Build Docker Image - run: docker build . --file EstateManagementUI/Dockerfile --tag estatemanagementui:latest +# - name: Build Docker Image +# run: docker build . --file EstateManagementUI/Dockerfile --tag estatemanagementui:latest - - name: Restore Nuget Packages - run: dotnet restore EstateManagementUI.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} +# - name: Restore Nuget Packages +# run: dotnet restore EstateManagementUI.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} - - name: Run Integration Tests (Chrome) - env: - Browser: Chrome - IsCI: true - run: dotnet test "EstateManagementUI.IntegrationTests\EstateManagementUI.IntegrationTests.csproj" --filter Category=PRTest +# - name: Run Integration Tests (Chrome) +# env: +# Browser: Chrome +# IsCI: true +# run: dotnet test "EstateManagementUI.IntegrationTests\EstateManagementUI.IntegrationTests.csproj" --filter Category=PRTest - - uses: actions/upload-artifact@v4.4.0 - if: ${{ failure() }} - with: - name: chrometracelogs - path: /home/txnproc/trace/ - - edgetests: - name: "Build and Test UI - Edge" - env: - ASPNETCORE_ENVIRONMENT: "Production" - NODE_VERSION: '14.x' # set this to the node version to use - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2.3.4 - - - name: Trust Certificate - run: | - sudo apt-get install expect - certPath="$GITHUB_WORKSPACE/Certificates/aspnetapp-root-cert.pfx" - password="password" - - # Use expect to automate the passphrase input - expect -c " - spawn openssl pkcs12 -in \"$certPath\" -clcerts -nokeys -out certificate.crt -password pass:\"$password\" - expect \"Enter Import Password:\" { send \"$password\n\" } - expect eof - " - - expect -c " - spawn openssl pkcs12 -in \"$certPath\" -nocerts -out private.key -password pass:\"$password\" - expect \"Enter Import Password:\" { send \"$password\n\" } - expect eof - " - - sudo cp certificate.crt /usr/local/share/ca-certificates/aspnetapp-root-cert.crt - sudo update-ca-certificates - - sudo cp private.key /etc/ssl/private/aspnetapp-root-cert.key - sudo chmod 400 /etc/ssl/private/aspnetapp-root-cert.key +# - uses: actions/upload-artifact@v4.4.0 +# if: ${{ failure() }} +# with: +# name: chrometracelogs +# path: /home/txnproc/trace/ + +# edgetests: +# name: "Build and Test UI - Edge" +# env: +# ASPNETCORE_ENVIRONMENT: "Production" +# NODE_VERSION: '14.x' # set this to the node version to use + +# runs-on: ubuntu-latest + +# steps: +# - uses: actions/checkout@v2.3.4 + +# - name: Trust Certificate +# run: | +# sudo apt-get install expect +# certPath="$GITHUB_WORKSPACE/Certificates/aspnetapp-root-cert.pfx" +# password="password" + +# # Use expect to automate the passphrase input +# expect -c " +# spawn openssl pkcs12 -in \"$certPath\" -clcerts -nokeys -out certificate.crt -password pass:\"$password\" +# expect \"Enter Import Password:\" { send \"$password\n\" } +# expect eof +# " + +# expect -c " +# spawn openssl pkcs12 -in \"$certPath\" -nocerts -out private.key -password pass:\"$password\" +# expect \"Enter Import Password:\" { send \"$password\n\" } +# expect eof +# " + +# sudo cp certificate.crt /usr/local/share/ca-certificates/aspnetapp-root-cert.crt +# sudo update-ca-certificates + +# sudo cp private.key /etc/ssl/private/aspnetapp-root-cert.key +# sudo chmod 400 /etc/ssl/private/aspnetapp-root-cert.key - - name: Build Docker Image - run: docker build . --file EstateManagementUI/Dockerfile --tag estatemanagementui:latest +# - name: Build Docker Image +# run: docker build . --file EstateManagementUI/Dockerfile --tag estatemanagementui:latest - - name: Restore Nuget Packages - run: dotnet restore EstateManagementUI.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} +# - name: Restore Nuget Packages +# run: dotnet restore EstateManagementUI.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} - - name: Run Integration Tests (Edge) - env: - Browser: Edge - IsCI: true - run: dotnet test "EstateManagementUI.IntegrationTests\EstateManagementUI.IntegrationTests.csproj" --filter Category=PRTest +# - name: Run Integration Tests (Edge) +# env: +# Browser: Edge +# IsCI: true +# run: dotnet test "EstateManagementUI.IntegrationTests\EstateManagementUI.IntegrationTests.csproj" --filter Category=PRTest - - uses: actions/upload-artifact@v4.4.0 - if: ${{ failure() }} - with: - name: edgetracelogs - path: /home/txnproc/trace/ - - fireefoxtests: - name: "Build and Test UI - Firefox" - env: - ASPNETCORE_ENVIRONMENT: "Production" - NODE_VERSION: '14.x' # set this to the node version to use - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2.3.4 - - - name: Trust Certificate - run: | - sudo apt-get install expect - certPath="$GITHUB_WORKSPACE/Certificates/aspnetapp-root-cert.pfx" - password="password" - - # Use expect to automate the passphrase input - expect -c " - spawn openssl pkcs12 -in \"$certPath\" -clcerts -nokeys -out certificate.crt -password pass:\"$password\" - expect \"Enter Import Password:\" { send \"$password\n\" } - expect eof - " - - expect -c " - spawn openssl pkcs12 -in \"$certPath\" -nocerts -out private.key -password pass:\"$password\" - expect \"Enter Import Password:\" { send \"$password\n\" } - expect eof - " - - sudo cp certificate.crt /usr/local/share/ca-certificates/aspnetapp-root-cert.crt - sudo update-ca-certificates - - sudo cp private.key /etc/ssl/private/aspnetapp-root-cert.key - sudo chmod 400 /etc/ssl/private/aspnetapp-root-cert.key +# - uses: actions/upload-artifact@v4.4.0 +# if: ${{ failure() }} +# with: +# name: edgetracelogs +# path: /home/txnproc/trace/ + +# fireefoxtests: +# name: "Build and Test UI - Firefox" +# env: +# ASPNETCORE_ENVIRONMENT: "Production" +# NODE_VERSION: '14.x' # set this to the node version to use + +# runs-on: ubuntu-latest + +# steps: +# - uses: actions/checkout@v2.3.4 + +# - name: Trust Certificate +# run: | +# sudo apt-get install expect +# certPath="$GITHUB_WORKSPACE/Certificates/aspnetapp-root-cert.pfx" +# password="password" + +# # Use expect to automate the passphrase input +# expect -c " +# spawn openssl pkcs12 -in \"$certPath\" -clcerts -nokeys -out certificate.crt -password pass:\"$password\" +# expect \"Enter Import Password:\" { send \"$password\n\" } +# expect eof +# " + +# expect -c " +# spawn openssl pkcs12 -in \"$certPath\" -nocerts -out private.key -password pass:\"$password\" +# expect \"Enter Import Password:\" { send \"$password\n\" } +# expect eof +# " + +# sudo cp certificate.crt /usr/local/share/ca-certificates/aspnetapp-root-cert.crt +# sudo update-ca-certificates + +# sudo cp private.key /etc/ssl/private/aspnetapp-root-cert.key +# sudo chmod 400 /etc/ssl/private/aspnetapp-root-cert.key - - name: Build Docker Image - run: docker build . --file EstateManagementUI/Dockerfile --tag estatemanagementui:latest +# - name: Build Docker Image +# run: docker build . --file EstateManagementUI/Dockerfile --tag estatemanagementui:latest - - name: Restore Nuget Packages - run: dotnet restore EstateManagementUI.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} +# - name: Restore Nuget Packages +# run: dotnet restore EstateManagementUI.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} - - name: Run Integration Tests (Firefox) - env: - Browser: Firefox - IsCI: true - run: dotnet test "EstateManagementUI.IntegrationTests\EstateManagementUI.IntegrationTests.csproj" --filter Category=PRTest +# - name: Run Integration Tests (Firefox) +# env: +# Browser: Firefox +# IsCI: true +# run: dotnet test "EstateManagementUI.IntegrationTests\EstateManagementUI.IntegrationTests.csproj" --filter Category=PRTest - - uses: actions/upload-artifact@v4.4.0 - if: ${{ failure() }} - with: - name: firefoxtracelogs - path: /home/txnproc/trace/ \ No newline at end of file +# - uses: actions/upload-artifact@v4.4.0 +# if: ${{ failure() }} +# with: +# name: firefoxtracelogs +# path: /home/txnproc/trace/ \ No newline at end of file diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Home.razor b/EstateManagementUI.BlazorServer/Components/Pages/Home.razor index 49c10a8c..cf82eb75 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Home.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Home.razor @@ -1,8 +1,17 @@ @page "/" +@rendermode InteractiveServer +@using MediatR @using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using EstateManagementUI.BlazorServer.Permissions +@using EstateManagementUI.BlazorServer.Requests +@using EstateManagementUI.BlazorServer.Models +@using static EstateManagementUI.BlazorServer.Requests.Queries @inject IMediator Mediator @inject AuthenticationStateProvider AuthenticationStateProvider @inject NavigationManager NavigationManager +@inject IJSRuntime JSRuntime +@inject IPermissionService PermissionService Dashboard @@ -18,127 +27,207 @@ - -
- -
-
- - + @if (isAdministrator) + { + +
+
+ + -
-
- Total Estates - 1 +

Welcome, Administrator

+

You have administrative access to manage system permissions and settings.

- - -
-
- - - -
-
- Total Merchants - 24 + } + else if (isLoading) + { + +
+
+
+ } + else if (!string.IsNullOrEmpty(errorMessage)) + { + +
+
+
+ + + +
+

Error Loading Dashboard

+

@errorMessage

+
+
+ } + else + { + +
+ +
+
+ + + +
+
+ Merchants with Sales (Last Hour) + @(merchantKpi?.MerchantsWithSaleInLastHour ?? 0) +
+
- -
-
- - - + +
+
+ + + +
+
+ Merchants with No Sales Today + @(merchantKpi?.MerchantsWithNoSaleToday ?? 0) +
-
- Total Contracts - 48 + + +
+
+ + + +
+
+ Merchants with No Sales (7 Days) + @(merchantKpi?.MerchantsWithNoSaleInLast7Days ?? 0) +
- -
-
- - - -
-
- Total Operators - 12 -
+ +
+ + + @if (comparisonDates != null) + { + @foreach (var date in comparisonDates) + { + + } + } + + Current: @_selectedComparisonDate
-
- -
- -
-
-

Quick Links

-
-
-
- -
- - - + +
+ + - -
-
-

System Status

-
-
-
-
-
-
- Estate Management + +
+
+

Failed Sales (Low Credit)

+
+
+

Today

+

@((todaysFailedSales?.TodaysSalesValue ?? 0).ToString("C"))

+

@(todaysFailedSales?.TodaysSalesCount ?? 0) transactions

- Active -
-
-
-
- Transaction Processing +
+

@GetComparisonLabel()

+

@((todaysFailedSales?.ComparisonSalesValue ?? 0).ToString("C"))

+

@(todaysFailedSales?.ComparisonSalesCount ?? 0) transactions

- Active
-
-
-
- File Processing +
+
+ Variance: + @GetFailedSalesVarianceDisplay()
- Active
-
+ + + @if (recentMerchants != null && recentMerchants.Any()) + { +
+
+

Recently Created Merchants

+
+
+
+ @foreach (var merchant in recentMerchants.Take(5)) + { +
+
+
+ + + +
+
+

@merchant.MerchantName

+

@merchant.MerchantReference

+
+
+ View +
+ } +
+
+
+ } + }
@code { + // Response code for low credit failures + private const string LOW_CREDIT_RESPONSE_CODE = "1008"; + + private bool isLoading = true; + private bool isAdministrator = false; + 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() { + await LogToConsole("OnInitializedAsync START"); var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var user = authState.User; @@ -148,7 +237,249 @@ NavigationManager.NavigateTo("/entry", replace: true); return; } + + // Check if user is Administrator + var role = await PermissionService.GetUserRoleAsync(); + isAdministrator = role == "Administrator"; + await LogToConsole($"User role: {role}, isAdministrator: {isAdministrator}"); + + // Only load data if not administrator + if (!isAdministrator) + { + await LoadDashboardData(); + } + else + { + isLoading = false; + } await base.OnInitializedAsync(); } + + private async Task LoadDashboardData() + { + await LogToConsole($"LoadDashboardData START - selectedDate: {_selectedComparisonDate}"); + try + { + isLoading = true; + errorMessage = null; + StateHasChanged(); + + var correlationId = new CorrelationId(Guid.NewGuid()); + // Note: These are stubbed values used throughout the test environment + // In production, these would come from the authentication context + var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); + var accessToken = "stubbed-token"; + + // Load comparison dates first (only if not already loaded) + if (comparisonDates == null || !comparisonDates.Any()) + { + var comparisonDatesResult = await Mediator.Send(new GetComparisonDatesQuery(correlationId, accessToken, estateId)); + if (comparisonDatesResult.IsSuccess) + { + comparisonDates = 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 var comparisonDate)) + { + // Fallback to a week ago if parse fails + comparisonDate = DateTime.Now.AddDays(-7); + } + + // Load all dashboard data in parallel + var kpiTask = Mediator.Send(new GetMerchantKpiQuery(correlationId, accessToken, estateId)); + var salesTask = Mediator.Send(new GetTodaysSalesQuery(correlationId, accessToken, estateId, comparisonDate)); + var failedSalesTask = Mediator.Send(new GetTodaysFailedSalesQuery(correlationId, accessToken, estateId, LOW_CREDIT_RESPONSE_CODE, comparisonDate)); + var merchantsTask = Mediator.Send(new GetMerchantsQuery(correlationId, accessToken, estateId)); + + await Task.WhenAll(kpiTask, salesTask, failedSalesTask, merchantsTask); + + // Process results + if (kpiTask.Result.IsSuccess) + merchantKpi = kpiTask.Result.Data; + + if (salesTask.Result.IsSuccess) + todaysSales = salesTask.Result.Data; + + if (failedSalesTask.Result.IsSuccess) + todaysFailedSales = failedSalesTask.Result.Data; + + if (merchantsTask.Result.IsSuccess) + // Note: API returns merchants in creation order (newest first) + // If ordering is incorrect, would need CreatedDate field in the model + recentMerchants = merchantsTask.Result.Data?.ToList(); + } + catch (Exception ex) + { + errorMessage = $"Failed to load dashboard data: {ex.Message}"; + } + finally + { + isLoading = false; + StateHasChanged(); + await LogToConsole("LoadDashboardData END"); + } + } + private async Task OnComparisonDateChanged() + { + changeEventCounter++; + await LogToConsole($"🔥 OnComparisonDateChanged FIRED! Count: {changeEventCounter}, New value: {_selectedComparisonDate}"); + + // This is called after _selectedComparisonDate is updated by @bind-Value + if (!isAdministrator) + { + await LogToConsole($"Loading dashboard data for date: {_selectedComparisonDate}"); + await LoadDashboardData(); + StateHasChanged(); + await LogToConsole("Dashboard data reload complete"); + } + else + { + await LogToConsole("User is administrator - skipping data reload"); + } + } + + 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 + } + } + + + private string GetComparisonLabel() + { + if (comparisonDates == null) return "Comparison"; + if (!DateTime.TryParse(_selectedComparisonDate, out var date)) + return "Comparison"; + var comparisonDate = comparisonDates.FirstOrDefault(d => d.Date.Date == date.Date); + return comparisonDate?.Description ?? date.ToString("MMM dd"); + } + + // Sales comparison color methods + private decimal GetSalesVariance() + { + 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; + } + + private string GetSalesBackgroundClass() + { + var variance = GetSalesVariance(); + if (variance < 0) return "bg-red-50"; // Worse + if (variance == 0) return "bg-blue-50"; // Same + if (variance > 0 && variance < 0.2m) return "bg-yellow-50"; // Slightly better + return "bg-green-50"; // Much better + } + + private string GetSalesTextClass(double opacity = 1.0) + { + var variance = GetSalesVariance(); + if (opacity < 1.0) + { + if (variance < 0) return "text-red-700"; + if (variance == 0) return "text-blue-700"; + if (variance > 0 && variance < 0.2m) return "text-yellow-700"; + return "text-green-700"; + } + if (variance < 0) return "text-red-900"; + if (variance == 0) return "text-blue-900"; + if (variance > 0 && variance < 0.2m) return "text-yellow-900"; + return "text-green-900"; + } + + private string GetSalesBorderClass() + { + var variance = GetSalesVariance(); + if (variance < 0) return "border-red-200"; + if (variance == 0) return "border-blue-200"; + if (variance > 0 && variance < 0.2m) return "border-yellow-200"; + return "border-green-200"; + } + + private string GetSalesVarianceDisplay() + { + var variance = GetSalesVariance(); + // Special case: comparison was 0, now has sales + if (variance >= 999m) return "NEW"; + var percentageChange = variance * 100; + var sign = variance > 0 ? "+" : ""; + return $"{sign}{percentageChange:F1}%"; + } + + // Failed sales comparison color methods (reversed logic) + private decimal GetFailedSalesVariance() + { + if (todaysFailedSales == null) return 0; + if (todaysFailedSales.ComparisonSalesValue == 0) + { + // If comparison is 0 and today is 0, no change + if (todaysFailedSales.TodaysSalesValue == 0) return 0; + // If comparison is 0 but today has failures, treat as maximum negative change (bad!) + return todaysFailedSales.TodaysSalesValue > 0 ? 999m : 0; + } + return (todaysFailedSales.TodaysSalesValue - todaysFailedSales.ComparisonSalesValue) / todaysFailedSales.ComparisonSalesValue; + } + + private string GetFailedSalesBackgroundClass() + { + var variance = GetFailedSalesVariance(); + if (variance < 0) return "bg-green-50"; // Good - fewer failures + if (variance == 0) return "bg-blue-50"; // Same + if (variance > 0 && variance < 0.2m) return "bg-yellow-50"; // Slightly worse + return "bg-red-50"; // Much worse + } + + private string GetFailedSalesTextClass(double opacity = 1.0) + { + var variance = GetFailedSalesVariance(); + if (opacity < 1.0) + { + if (variance < 0) return "text-green-700"; + if (variance == 0) return "text-blue-700"; + if (variance > 0 && variance < 0.2m) return "text-yellow-700"; + return "text-red-700"; + } + if (variance < 0) return "text-green-900"; + if (variance == 0) return "text-blue-900"; + if (variance > 0 && variance < 0.2m) return "text-yellow-900"; + return "text-red-900"; + } + + private string GetFailedSalesBorderClass() + { + var variance = GetFailedSalesVariance(); + if (variance < 0) return "border-green-200"; + if (variance == 0) return "border-blue-200"; + if (variance > 0 && variance < 0.2m) return "border-yellow-200"; + return "border-red-200"; + } + + private string GetFailedSalesVarianceDisplay() + { + var variance = GetFailedSalesVariance(); + // Special case: comparison was 0, now has failures + if (variance >= 999m) return "NEW"; + var percentageChange = variance * 100; + var sign = variance > 0 ? "+" : ""; + return $"{sign}{percentageChange:F1}%"; + } } diff --git a/EstateManagementUI.BlazorServer/NuGet.Config b/EstateManagementUI.BlazorServer/NuGet.Config deleted file mode 100644 index 3086e202..00000000 --- a/EstateManagementUI.BlazorServer/NuGet.Config +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/EstateManagementUI.BlazorServer/Services/TestMediatorService.cs b/EstateManagementUI.BlazorServer/Services/TestMediatorService.cs index 9f85ac3b..0fd50c7c 100644 --- a/EstateManagementUI.BlazorServer/Services/TestMediatorService.cs +++ b/EstateManagementUI.BlazorServer/Services/TestMediatorService.cs @@ -43,12 +43,12 @@ public Task Send(IRequest request, Cancellation // Dashboard Queries - return mock data Queries.GetComparisonDatesQuery => Task.FromResult((TResponse)(object)Result>.Success(GetMockComparisonDates())), - Queries.GetTodaysSalesQuery => Task.FromResult((TResponse)(object)Result.Success(GetMockTodaysSales())), + Queries.GetTodaysSalesQuery query => Task.FromResult((TResponse)(object)Result.Success(GetMockTodaysSales(query.ComparisonDate))), Queries.GetTodaysSettlementQuery => Task.FromResult((TResponse)(object)Result.Success(GetMockTodaysSettlement())), Queries.GetTodaysSalesCountByHourQuery => Task.FromResult((TResponse)(object)Result>.Success(GetMockSalesCountByHour())), Queries.GetTodaysSalesValueByHourQuery => Task.FromResult((TResponse)(object)Result>.Success(GetMockSalesValueByHour())), Queries.GetMerchantKpiQuery => Task.FromResult((TResponse)(object)Result.Success(GetMockMerchantKpi())), - Queries.GetTodaysFailedSalesQuery => Task.FromResult((TResponse)(object)Result.Success(GetMockTodaysSales())), + Queries.GetTodaysFailedSalesQuery query => Task.FromResult((TResponse)(object)Result.Success(GetMockTodaysFailedSales(query.ComparisonDate))), Queries.GetTopProductDataQuery => Task.FromResult((TResponse)(object)Result>.Success(GetMockTopProducts())), Queries.GetBottomProductDataQuery => Task.FromResult((TResponse)(object)Result>.Success(GetMockBottomProducts())), Queries.GetTopMerchantDataQuery => Task.FromResult((TResponse)(object)Result>.Success(GetMockTopMerchants())), @@ -370,21 +370,102 @@ private Result ExecuteMakeMerchantDeposit(Commands.MakeMerchantDepositCommand cm IgnoredLines = 0 }; - private static List GetMockComparisonDates() => new() + private static List GetMockComparisonDates() { - new ComparisonDateModel { Date = DateTime.Today, Description = "Today" }, - new ComparisonDateModel { Date = DateTime.Today.AddDays(-1), Description = "Yesterday" } - }; + var dates = new List(); + var today = DateTime.Today; + + // Add primary options + dates.Add(new ComparisonDateModel { Date = today.AddDays(-1), Description = "Yesterday" }); + dates.Add(new ComparisonDateModel { Date = today.AddDays(-7), Description = $"Last Week ({today.AddDays(-7).DayOfWeek})" }); + dates.Add(new ComparisonDateModel { Date = today.AddMonths(-1), Description = $"Last Month ({today.AddMonths(-1).Day}{GetDaySuffix(today.AddMonths(-1).Day)})" }); + + // Add other dates, excluding those already covered (yesterday, last week's exact day, last month's exact day) + var excludeDates = new HashSet + { + today.AddDays(-1).Date, // Yesterday + today.AddDays(-7).Date, // Last week + today.AddMonths(-1).Date // Last month + }; + + // Add dates from 2-30 days ago (excluding already covered dates) + for (int i = 2; i <= 30; i++) + { + var date = today.AddDays(-i); + if (!excludeDates.Contains(date)) + { + dates.Add(new ComparisonDateModel + { + Date = date, + Description = $"{i} days ago ({date:MMM d})" + }); + } + } + + return dates; + } + + private static string GetDaySuffix(int day) + { + return day switch + { + 1 or 21 or 31 => "st", + 2 or 22 => "nd", + 3 or 23 => "rd", + _ => "th" + }; + } - private static TodaysSalesModel GetMockTodaysSales() => new() + private static TodaysSalesModel GetMockTodaysSales(DateTime comparisonDate) { - ComparisonSalesCount = 450, - ComparisonSalesValue = 125000.00m, - ComparisonAverageValue = 277.78m, - TodaysSalesCount = 523, - TodaysSalesValue = 145000.00m, - TodaysAverageValue = 277.24m - }; + // Generate different data based on how many days ago the comparison date is + var daysAgo = (DateTime.Today - comparisonDate.Date).Days; + + // Use days ago to create variance in the data + // The further back, the more the comparison differs + var baseComparisonValue = 100000.00m; + var baseTodayValue = 145000.00m; + + // Add some variance based on the comparison date + var comparisonMultiplier = 1.0m + (daysAgo * 0.02m); // 2% increase per day back + var comparisonValue = baseComparisonValue * comparisonMultiplier; + var comparisonCount = (int)(400 + (daysAgo * 5)); // 5 more transactions per day back + + return new TodaysSalesModel + { + ComparisonSalesCount = comparisonCount, + ComparisonSalesValue = comparisonValue, + ComparisonAverageValue = comparisonValue / comparisonCount, + TodaysSalesCount = 523, + TodaysSalesValue = baseTodayValue, + TodaysAverageValue = baseTodayValue / 523 + }; + } + + private static TodaysSalesModel GetMockTodaysFailedSales(DateTime comparisonDate) + { + // Generate different failed sales data based on comparison date + var daysAgo = (DateTime.Today - comparisonDate.Date).Days; + + // Failed sales should ideally decrease over time (improving) + var baseComparisonValue = 5000.00m; + var baseTodayValue = 850.00m; + + // More failures in the past, fewer now (showing improvement) + var comparisonMultiplier = 1.0m + (daysAgo * 0.05m); // 5% more failures per day back + var comparisonValue = baseComparisonValue * comparisonMultiplier; + var comparisonCount = (int)(25 + (daysAgo * 2)); // 2 more failed transactions per day back + + return new TodaysSalesModel + { + ComparisonSalesCount = comparisonCount, + ComparisonSalesValue = comparisonValue, + ComparisonAverageValue = comparisonValue / comparisonCount, + TodaysSalesCount = 15, + TodaysSalesValue = baseTodayValue, + TodaysAverageValue = baseTodayValue / 15 + }; + } private static TodaysSettlementModel GetMockTodaysSettlement() => new() {