diff --git a/TransactionProcessor.Mobile.BusinessLogic.Tests/RequestHandlerTests/ReportRequestHandlerTests.cs b/TransactionProcessor.Mobile.BusinessLogic.Tests/RequestHandlerTests/ReportRequestHandlerTests.cs new file mode 100644 index 000000000..d02db4ca3 --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic.Tests/RequestHandlerTests/ReportRequestHandlerTests.cs @@ -0,0 +1,24 @@ +using MediatR; +using SimpleResults; +using Shouldly; +using TransactionProcessor.Mobile.BusinessLogic.RequestHandlers; +using TransactionProcessor.Mobile.BusinessLogic.Requests; +using TransactionProcessor.Mobile.BusinessLogic.Models; + +namespace TransactionProcessor.Mobile.BusinessLogic.Tests.RequestHandlerTests; + +public class ReportRequestHandlerTests +{ + [Fact] + public async Task GetDailyPerformanceSummaryQuery_ReturnsMockedSummaryForToday() + { + ReportRequestHandler handler = new(); + + Result result = await handler.Handle(new ReportQueries.GetDailyPerformanceSummaryQuery(PerformanceSummaryPeriod.Today), CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + result.Data.Period.ShouldBe(PerformanceSummaryPeriod.Today); + result.Data.Metrics.ShouldContain(m => m.Title == "Total transaction count" && m.Value == "48"); + result.Data.DrillDownTransactions.ShouldContain(t => t.Reference == "TXN-00048"); + } +} diff --git a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/Reports/DailyPerformanceSummaryPageViewModelTests.cs b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/Reports/DailyPerformanceSummaryPageViewModelTests.cs new file mode 100644 index 000000000..49a249f07 --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/Reports/DailyPerformanceSummaryPageViewModelTests.cs @@ -0,0 +1,67 @@ +using MediatR; +using Moq; +using SimpleResults; +using Shouldly; +using TransactionProcessor.Mobile.BusinessLogic.Common; +using TransactionProcessor.Mobile.BusinessLogic.Models; +using TransactionProcessor.Mobile.BusinessLogic.Requests; +using TransactionProcessor.Mobile.BusinessLogic.Services; +using TransactionProcessor.Mobile.BusinessLogic.UIServices; +using TransactionProcessor.Mobile.BusinessLogic.ViewModels.Reports; + +namespace TransactionProcessor.Mobile.BusinessLogic.Tests.ViewModelTests.Reports; + +public class DailyPerformanceSummaryPageViewModelTests +{ + private readonly Mock Mediator; + private readonly Mock NavigationService; + private readonly Mock ApplicationCache; + private readonly Mock DialogService; + private readonly Mock DeviceService; + private readonly Mock NavigationParameterService; + private readonly DailyPerformanceSummaryPageViewModel ViewModel; + + public DailyPerformanceSummaryPageViewModelTests() + { + this.Mediator = new Mock(); + this.Mediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(DailyPerformanceSummaryModel.CreateMock(PerformanceSummaryPeriod.Today))); + this.NavigationService = new Mock(); + this.ApplicationCache = new Mock(); + this.DialogService = new Mock(); + this.DeviceService = new Mock(); + this.NavigationParameterService = new Mock(); + + this.ViewModel = new DailyPerformanceSummaryPageViewModel(this.Mediator.Object, + this.NavigationService.Object, + this.ApplicationCache.Object, + this.DialogService.Object, + this.DeviceService.Object, + this.NavigationParameterService.Object); + } + + [Fact] + public async Task Initialise_LoadsMockedSummaryForToday() + { + await this.ViewModel.Initialise(CancellationToken.None); + + this.ViewModel.SelectedPeriod.ShouldBe(PerformanceSummaryPeriod.Today); + this.ViewModel.SummaryCards.Count.ShouldBe(6); + this.ViewModel.TopSummaryCards.Count.ShouldBe(4); + this.ViewModel.TopSummaryCardsRow1.Count.ShouldBe(2); + this.ViewModel.TopSummaryCardsRow2.Count.ShouldBe(2); + this.ViewModel.TopSummaryCardsRow1.Select(card => card.Title).ShouldBe(new[] + { + "Total transaction count", + "Total transaction value", + }); + this.ViewModel.TopSummaryCardsRow2.Select(card => card.Title).ShouldBe(new[] + { + "Successful transaction count", + "Failed transaction count", + }); + this.ViewModel.DrillDownTransactions.Count.ShouldBe(3); + this.ViewModel.IsLoading.ShouldBeFalse(); + } +} diff --git a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/Reports/ReportsPageViewModelTests.cs b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/Reports/ReportsPageViewModelTests.cs index 5cedf829d..9a4582525 100644 --- a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/Reports/ReportsPageViewModelTests.cs +++ b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/Reports/ReportsPageViewModelTests.cs @@ -54,14 +54,10 @@ public async Task ReportsPageViewModel_OptionSelectedCommand_Execute_IsExecuted( switch(selectedIndex){ case 0: - this.NavigationService.Verify(v => v.GoToReportsSalesAnalysis(), Times.Once); - break; - case 1: - this.NavigationService.Verify(v => v.GoToReportsBalanceAnalysis(), Times.Once); + this.NavigationService.Verify(v => v.GoToDailyPerformanceSummaryPage(), Times.Once); break; default: - this.NavigationService.Verify(v => v.GoToReportsSalesAnalysis(), Times.Never); - this.NavigationService.Verify(v => v.GoToReportsBalanceAnalysis(), Times.Never); + this.NavigationService.Verify(v => v.GoToDailyPerformanceSummaryPage(), Times.Never); break; } } @@ -70,7 +66,7 @@ public async Task ReportsPageViewModel_OptionSelectedCommand_Execute_IsExecuted( public async Task ReportsPageViewModel_Initialise_IsInitialised() { await this.ViewModel.Initialise(CancellationToken.None); - this.ViewModel.ReportsMenuOptions.Count.ShouldBe(2); + this.ViewModel.ReportsMenuOptions.Count.ShouldBe(1); } [Fact] @@ -81,117 +77,4 @@ public async Task ReportsPageViewModel_BackButtonCommand_HomePageIsShown() this.NavigationService.Verify(n => n.GoToHome(), Times.Once); } } - - public class ReportsSalesAnalysisPageViewModelTests{ - private readonly ReportsSalesAnalysisPageViewModel ViewModel; - private readonly Mock NavigationService; - private readonly Mock NavigationParameterService; - private readonly Mock ApplicationCache; - private readonly Mock DialogService; - private readonly Mock DeviceService; - - public ReportsSalesAnalysisPageViewModelTests(){ - this.NavigationService = new Mock(); - this.NavigationParameterService = new Mock(); - this.ApplicationCache = new Mock(); - this.DialogService = new Mock(); - this.DeviceService = new Mock(); - - this.ViewModel = new ReportsSalesAnalysisPageViewModel(this.NavigationService.Object, - this.ApplicationCache.Object, - this.DialogService.Object, - this.DeviceService.Object, - this.NavigationParameterService.Object); - } - - [Fact] - public async Task ReportsSalesAnalysisPageViewModel_Initialise_Execute_IsExecuted(){ - await this.ViewModel.Initialise(CancellationToken.None); - - this.ViewModel.ComparisonDates.Count.ShouldBe(3); - } - - [Fact] - public async Task ReportsSalesAnalysisPageViewModel_ComparisonDatePickerSelectedIndexChangedCommand_HomePageIsShown(){ - ComparisonDate comparisonDate = new ComparisonDate(DateTime.Now.AddDays(-1), "Yesterday"); - this.ViewModel.SelectedItem = comparisonDate; - this.ViewModel.ComparisonDatePickerSelectedIndexChangedCommand.Execute(comparisonDate); - - // Nothing to actually verify yet here - this.ViewModel.SalesAnalysisList.Count.ShouldBe(2); - } - - [Fact] - public void ReportsSalesAnalysisPageViewModel_ComparisonDatePickerSelectedIndexChangedCommand_WithoutSelectedItem_ReturnsEmptyList() - { - this.ViewModel.ComparisonDatePickerSelectedIndexChangedCommand.Execute(null); - - this.ViewModel.SalesAnalysisList.ShouldNotBeNull(); - this.ViewModel.SalesAnalysisList.ShouldBeEmpty(); - } - - [Fact] - public async Task ReportsSalesAnalysisPageViewModel_BackButtonCommand_HomePageIsShown() - { - this.ViewModel.BackButtonCommand.Execute(null); - - this.NavigationService.Verify(n => n.GoBack(), Times.Once); - } - } - - public class ReportsBalanceAnalysisPageViewModelTests - { - private ReportsBalanceAnalysisPageViewModel ViewModel; - private Mock NavigationService; - private Mock NavigationParameterService; - private Mock ApplicationCache; - private Mock DialogService; - private readonly Mock DeviceService; - - public ReportsBalanceAnalysisPageViewModelTests() - { - this.NavigationService = new Mock(); - this.NavigationParameterService = new Mock(); - this.ApplicationCache = new Mock(); - this.DialogService = new Mock(); - this.DeviceService = new Mock(); - - this.ViewModel = new ReportsBalanceAnalysisPageViewModel(this.NavigationService.Object, - this.ApplicationCache.Object, - this.DialogService.Object, - this.DeviceService.Object, - this.NavigationParameterService.Object); - } - - [Fact] - public async Task ReportsBalanceAnalysisPageViewModel_Initialise_Execute_IsExecuted() - { - await this.ViewModel.Initialise(CancellationToken.None); - - this.ViewModel.XAxes.ShouldNotBeNull(); - this.ViewModel.YAxes.ShouldNotBeNull(); - this.ViewModel.TooltipFindingStrategy.ShouldBe(TooltipFindingStrategy.CompareOnlyX); - this.ViewModel.TooltipPosition.ShouldBe(TooltipPosition.Top); - this.ViewModel.Series.ShouldNotBeNull(); - } - - //[Fact] - //public async Task ReportsSalesAnalysisPageViewModel_ComparisonDatePickerSelectedIndexChangedCommand_HomePageIsShown() - //{ - // ComparisonDate comparisonDate = new ComparisonDate(DateTime.Now.AddDays(-1), "Yesterday"); - // this.viewModel.SelectedItem = comparisonDate; - // viewModel.ComparisonDatePickerSelectedIndexChangedCommand.Execute(comparisonDate); - - // // Nothing to actually verify yet here - // this.viewModel.SalesAnalysisList.Count.ShouldBe(2); - //} - - [Fact] - public async Task ReportsBalanceAnalysisPageViewModel_BackButtonCommand_HomePageIsShown() - { - this.ViewModel.BackButtonCommand.Execute(null); - - this.NavigationService.Verify(n => n.GoBack(), Times.Once); - } - } } diff --git a/TransactionProcessor.Mobile.BusinessLogic/Models/DailyPerformanceSummaryModel.cs b/TransactionProcessor.Mobile.BusinessLogic/Models/DailyPerformanceSummaryModel.cs new file mode 100644 index 000000000..c1cd666fc --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic/Models/DailyPerformanceSummaryModel.cs @@ -0,0 +1,74 @@ +namespace TransactionProcessor.Mobile.BusinessLogic.Models; + +public enum PerformanceSummaryPeriod +{ + Today = 0, + Yesterday = 1, + ThisWeek = 2, + MonthToDate = 3, +} + +public sealed record DailyPerformanceSummaryModel( + PerformanceSummaryPeriod Period, + DateTime FromDate, + DateTime ToDate, + string PeriodLabel, + IReadOnlyList Metrics, + IReadOnlyList DrillDownTransactions) +{ + public static DailyPerformanceSummaryModel CreateMock(PerformanceSummaryPeriod period) + { + DateTime today = DateTime.Today; + DateTime fromDate = period switch + { + PerformanceSummaryPeriod.Today => today, + PerformanceSummaryPeriod.Yesterday => today.AddDays(-1), + PerformanceSummaryPeriod.ThisWeek => StartOfWeek(today), + PerformanceSummaryPeriod.MonthToDate => new DateTime(today.Year, today.Month, 1), + _ => today, + }; + + DateTime toDate = period switch + { + PerformanceSummaryPeriod.Yesterday => today.AddDays(-1), + _ => today, + }; + + List metrics = new() + { + new DailyPerformanceMetricModel("Total transaction count", "48", "Processed today", DailyPerformanceMetricCategory.Total), + new DailyPerformanceMetricModel("Total transaction value", "10,250.00 KES", "Gross value", DailyPerformanceMetricCategory.Total), + new DailyPerformanceMetricModel("Successful transaction count", "44", "Completed successfully", DailyPerformanceMetricCategory.Success), + new DailyPerformanceMetricModel("Failed transaction count", "4", "Could not be completed", DailyPerformanceMetricCategory.Failure), + new DailyPerformanceMetricModel("Average transaction value", "213.54 KES", "Average across all transactions"), + new DailyPerformanceMetricModel("Top product", "Mobile Topup", "Highest volume"), + }; + + List drillDownTransactions = new() + { + new DailyPerformanceTransactionModel("TXN-00048", "Mobile Topup", "Success", "250.00 KES", today.AddHours(-1)), + new DailyPerformanceTransactionModel("TXN-00047", "Bill Payment", "Success", "1,500.00 KES", today.AddHours(-2)), + new DailyPerformanceTransactionModel("TXN-00046", "Voucher Issue", "Failed", "0.00 KES", today.AddHours(-3)), + }; + + return new DailyPerformanceSummaryModel(period, fromDate, toDate, period.ToString(), metrics, drillDownTransactions); + } + + private static DateTime StartOfWeek(DateTime date) + { + int daysSinceSunday = (int)date.DayOfWeek; + return date.Date.AddDays(-daysSinceSunday); + } +} + +public enum DailyPerformanceMetricCategory +{ + Neutral = 0, + Total = 1, + Success = 2, + Failure = 3, +} + +public sealed record DailyPerformanceMetricModel(string Title, string Value, string Description, DailyPerformanceMetricCategory Category = DailyPerformanceMetricCategory.Neutral); + +public sealed record DailyPerformanceTransactionModel(string Reference, string Product, string Status, string Amount, DateTime TransactionDateTime); diff --git a/TransactionProcessor.Mobile.BusinessLogic/RequestHandlers/ReportRequestHandler.cs b/TransactionProcessor.Mobile.BusinessLogic/RequestHandlers/ReportRequestHandler.cs new file mode 100644 index 000000000..c11e4ddc2 --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic/RequestHandlers/ReportRequestHandler.cs @@ -0,0 +1,15 @@ +using MediatR; +using SimpleResults; +using TransactionProcessor.Mobile.BusinessLogic.Models; +using TransactionProcessor.Mobile.BusinessLogic.Requests; + +namespace TransactionProcessor.Mobile.BusinessLogic.RequestHandlers; + +public sealed class ReportRequestHandler : IRequestHandler> +{ + public Task> Handle(ReportQueries.GetDailyPerformanceSummaryQuery request, CancellationToken cancellationToken) + { + DailyPerformanceSummaryModel summary = DailyPerformanceSummaryModel.CreateMock(request.Period); + return Task.FromResult(Result.Success(summary)); + } +} diff --git a/TransactionProcessor.Mobile.BusinessLogic/Requests/ReportQueries.cs b/TransactionProcessor.Mobile.BusinessLogic/Requests/ReportQueries.cs new file mode 100644 index 000000000..785739639 --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic/Requests/ReportQueries.cs @@ -0,0 +1,10 @@ +using MediatR; +using SimpleResults; +using TransactionProcessor.Mobile.BusinessLogic.Models; + +namespace TransactionProcessor.Mobile.BusinessLogic.Requests; + +public record ReportQueries +{ + public record GetDailyPerformanceSummaryQuery(PerformanceSummaryPeriod Period) : IRequest>; +} diff --git a/TransactionProcessor.Mobile.BusinessLogic/UIServices/INavigationService.cs b/TransactionProcessor.Mobile.BusinessLogic/UIServices/INavigationService.cs index 9dbc5ac5f..9f5f8e734 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/UIServices/INavigationService.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/UIServices/INavigationService.cs @@ -62,10 +62,9 @@ Task GoToBillPaymentPayBillPage(ProductDetails productDetails, Task GoToMyAccountContacts(); Task GoToMyAccountDetails(); - Task GoToReportsSalesAnalysis(); - Task GoToReportsBalanceAnalysis(); + Task GoToDailyPerformanceSummaryPage(); Task GoToTransactions(); #endregion -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/DailyPerformanceSummaryPageViewModel.cs b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/DailyPerformanceSummaryPageViewModel.cs new file mode 100644 index 000000000..a4ab9fa06 --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/DailyPerformanceSummaryPageViewModel.cs @@ -0,0 +1,157 @@ +using CommunityToolkit.Mvvm.Input; +using MediatR; +using SimpleResults; +using TransactionProcessor.Mobile.BusinessLogic.Common; +using TransactionProcessor.Mobile.BusinessLogic.Models; +using TransactionProcessor.Mobile.BusinessLogic.Requests; +using TransactionProcessor.Mobile.BusinessLogic.Services; +using TransactionProcessor.Mobile.BusinessLogic.UIServices; + +namespace TransactionProcessor.Mobile.BusinessLogic.ViewModels.Reports; + +public sealed partial class DailyPerformanceSummaryPageViewModel : ExtendedBaseViewModel +{ + private readonly IMediator Mediator; + private PerformanceSummaryPeriod selectedPeriod; + private PerformanceSummaryPeriodOption? selectedPeriodOption; + private bool isLoading; + private string? errorMessage; + private DailyPerformanceSummaryModel? summary; + + public DailyPerformanceSummaryPageViewModel(IMediator mediator, + INavigationService navigationService, + IApplicationCache applicationCache, + IDialogService dialogService, + IDeviceService deviceService, + INavigationParameterService navigationParameterService) : base(applicationCache, dialogService, navigationService, deviceService, navigationParameterService) + { + this.Mediator = mediator; + this.Title = "Daily Performance Summary"; + this.SelectedPeriod = PerformanceSummaryPeriod.Today; + this.PeriodOptions = Enum.GetValues() + .Select(period => new PerformanceSummaryPeriodOption(period, period.ToDisplayText())) + .ToList(); + this.SelectedPeriodOption = this.PeriodOptions.First(); + } + + public List PeriodOptions { get; } + + public PerformanceSummaryPeriod SelectedPeriod + { + get => this.selectedPeriod; + set => SetProperty(ref this.selectedPeriod, value); + } + + public PerformanceSummaryPeriodOption? SelectedPeriodOption + { + get => this.selectedPeriodOption; + set + { + if (SetProperty(ref this.selectedPeriodOption, value) && value is not null) + { + this.SelectedPeriod = value.Period; + } + } + } + + public bool IsLoading + { + get => this.isLoading; + set => SetProperty(ref this.isLoading, value); + } + + public string? ErrorMessage + { + get => this.errorMessage; + set => SetProperty(ref this.errorMessage, value); + } + + public DailyPerformanceSummaryModel? Summary + { + get => this.summary; + private set => SetProperty(ref this.summary, value); + } + + public IReadOnlyList SummaryCards => this.Summary?.Metrics ?? Array.Empty(); + + public IReadOnlyList TopSummaryCards => this.SummaryCards.Take(4).ToList(); + + public IReadOnlyList TopSummaryCardsRow1 => this.TopSummaryCards.Take(2).ToList(); + + public IReadOnlyList TopSummaryCardsRow2 => this.TopSummaryCards.Skip(2).Take(2).ToList(); + + public IReadOnlyList DrillDownTransactions => this.Summary?.DrillDownTransactions ?? Array.Empty(); + + public bool HasSummaryData => this.SummaryCards.Count > 0; + + public bool HasError => string.IsNullOrWhiteSpace(this.ErrorMessage) == false; + + public bool HasEmptyState => this.IsLoading == false && this.HasError == false && this.HasSummaryData == false; + + public override async Task Initialise(CancellationToken cancellationToken) + { + await base.Initialise(cancellationToken); + await this.LoadSummaryAsync(this.SelectedPeriod, cancellationToken); + } + + [RelayCommand] + private async Task PeriodChanged() + { + if (this.SelectedPeriodOption is not null) + { + this.SelectedPeriod = this.SelectedPeriodOption.Period; + } + + await this.LoadSummaryAsync(this.SelectedPeriod, CancellationToken.None); + } + + private async Task LoadSummaryAsync(PerformanceSummaryPeriod period, CancellationToken cancellationToken) + { + this.IsLoading = true; + this.ErrorMessage = null; + + try + { + Result result = await this.Mediator.Send(new ReportQueries.GetDailyPerformanceSummaryQuery(period), cancellationToken); + if (result.IsFailed) + { + this.Summary = null; + this.ErrorMessage = "Unable to load performance summary."; + return; + } + + this.Summary = result.Data; + } + catch + { + this.Summary = null; + this.ErrorMessage = "Unable to load performance summary."; + } + finally + { + this.IsLoading = false; + this.OnPropertyChanged(nameof(this.SummaryCards)); + this.OnPropertyChanged(nameof(this.TopSummaryCards)); + this.OnPropertyChanged(nameof(this.TopSummaryCardsRow1)); + this.OnPropertyChanged(nameof(this.TopSummaryCardsRow2)); + this.OnPropertyChanged(nameof(this.DrillDownTransactions)); + this.OnPropertyChanged(nameof(this.HasSummaryData)); + this.OnPropertyChanged(nameof(this.HasError)); + this.OnPropertyChanged(nameof(this.HasEmptyState)); + } + } +} + +public sealed record PerformanceSummaryPeriodOption(PerformanceSummaryPeriod Period, string DisplayText); + +internal static class PerformanceSummaryPeriodExtensions +{ + public static string ToDisplayText(this PerformanceSummaryPeriod period) => period switch + { + PerformanceSummaryPeriod.Today => "Today", + PerformanceSummaryPeriod.Yesterday => "Yesterday", + PerformanceSummaryPeriod.ThisWeek => "This Week", + PerformanceSummaryPeriod.MonthToDate => "Month to Date", + _ => period.ToString(), + }; +} diff --git a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/ReportsPageViewModel.cs b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/ReportsPageViewModel.cs index 1a6896b70..e3017a32b 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/ReportsPageViewModel.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/ReportsPageViewModel.cs @@ -45,11 +45,8 @@ public override async Task Initialise(CancellationToken cancellationToken) { this.ReportsMenuOptions = new List { new ListViewItem { - Title = "Sales Analysis" - }, - new ListViewItem { - Title = "Balance Analysis" - } + Title = "Daily Performance Summary" + }, }; await base.Initialise(cancellationToken); } @@ -62,8 +59,7 @@ private async Task OptionSelected(ItemSelected arg) Task navigationTask = selectedOption switch { - ReportsOptions.SalesAnalysis => this.NavigationService.GoToReportsSalesAnalysis(), - ReportsOptions.BalanceAnalysis => this.NavigationService.GoToReportsBalanceAnalysis(), + ReportsOptions.DailyPerformanceSummary => this.NavigationService.GoToDailyPerformanceSummaryPage(), _ => Task.Factory.StartNew(() => Logger.LogWarning($"Unsupported option selected {selectedOption}")) }; @@ -76,9 +72,7 @@ private async Task OptionSelected(ItemSelected arg) public enum ReportsOptions { - SalesAnalysis = 0, - - BalanceAnalysis = 1, + DailyPerformanceSummary = 0, } #endregion diff --git a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/ReportsSalesAnalysisPageViewModel.cs b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/ReportsSalesAnalysisPageViewModel.cs deleted file mode 100644 index fd59621ba..000000000 --- a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/Reports/ReportsSalesAnalysisPageViewModel.cs +++ /dev/null @@ -1,174 +0,0 @@ -using CommunityToolkit.Mvvm.Input; -using LiveChartsCore; -using LiveChartsCore.Defaults; -using LiveChartsCore.Kernel.Sketches; -using LiveChartsCore.Measure; -using LiveChartsCore.SkiaSharpView; -using System.Diagnostics.CodeAnalysis; -using TransactionProcessor.Mobile.BusinessLogic.Common; -using TransactionProcessor.Mobile.BusinessLogic.Services; -using TransactionProcessor.Mobile.BusinessLogic.UIServices; - -namespace TransactionProcessor.Mobile.BusinessLogic.ViewModels.Reports; - -public partial class ReportsSalesAnalysisPageViewModel : ExtendedBaseViewModel -{ - public ReportsSalesAnalysisPageViewModel(INavigationService navigationService, - IApplicationCache applicationCache, - IDialogService dialogService, - IDeviceService deviceService, - INavigationParameterService navigationParameterService) : base(applicationCache, dialogService, navigationService, deviceService, navigationParameterService) - { - this.Title = "Sales Analysis"; - } - - [RelayCommand] - private async Task ComparisonDatePickerSelectedIndexChanged() - { - await this.GetApiData(); - } - - private async Task GetApiData() - { - if (this.SelectedItem is null) - { - this.SalesAnalysisList = new List(); - return; - } - - // TODO: Initial api call to get data would be done here - List salesAnalysisList = new List(); - salesAnalysisList.Add(new SalesAnalysis("100.00 KES", "90.00 KES", "10%", "Sales Value", "Today's Sales", this.SelectedItem.DisplayText, "Variance:", "salesvalue.png")); - salesAnalysisList.Add(new SalesAnalysis("100", "90", "10%", "Sales Count", "Today's Sales", this.SelectedItem.DisplayText, "Variance:", "salescount.png")); - this.SalesAnalysisList = salesAnalysisList; - } - - private List comparisonDates; - - ComparisonDate _selectedItem; - - private List salesAnalysisList; - - public ComparisonDate SelectedItem - { - get => this._selectedItem; - set => SetProperty(ref this._selectedItem, value); - } - - public List SalesAnalysisList - { - get => this.salesAnalysisList; - set => this.SetProperty(ref this.salesAnalysisList, value); - } - - public List ComparisonDates - { - get => this.comparisonDates; - set => this.SetProperty(ref this.comparisonDates, value); - } - - public override async Task Initialise(CancellationToken cancellationToken) - { - await base.Initialise(cancellationToken); - // TODO: This list will come from an api call - List dates = new List(); - dates.Add(new ComparisonDate(new DateTime(2024, 1, 12), "Yesterday")); - dates.Add(new ComparisonDate(new DateTime(2024, 1, 11), "2024-01-11")); - dates.Add(new ComparisonDate(new DateTime(2024, 1, 10), "2024-01-10")); - this.ComparisonDates = dates; - } - -} - -[ExcludeFromCodeCoverage] -public record ComparisonDate(DateTime DateTime, String DisplayText); - -[ExcludeFromCodeCoverage] -public record SalesAnalysis(String TodaysValue, String ComparisonValue, String VarianceValue, String MainTitle, String TodaysTitle, String ComparisonTitle, String VarianceTitle, String Icon); - -public class ReportsBalanceAnalysisPageViewModel : ExtendedBaseViewModel -{ - private TooltipPosition tooltipPosition; - private List yAxes; - private List xAxes; - private ISeries[] series; - private TooltipFindingStrategy tooltipFindingStrategy; - - public ReportsBalanceAnalysisPageViewModel(INavigationService navigationService, - IApplicationCache applicationCache, - IDialogService dialogService, - IDeviceService deviceService, - INavigationParameterService navigationParameterService) : base(applicationCache, dialogService, navigationService, deviceService, navigationParameterService) - { - this.Title = "Balance Analysis"; - } - - public override async Task Initialise(CancellationToken cancellationToken) - { - await base.Initialise(cancellationToken); - - DateTimeAxis axis1 = new DateTimeAxis(TimeSpan.FromDays(1), (d) => d.ToString("dd/MM/yyyy")); - this.XAxes = new List{ - axis1 - }; - - Axis axis2 = new Axis(); - axis2.Labeler = Labelers.Currency; - - this.YAxes = new List{ - axis2 - }; - - this.TooltipFindingStrategy = LiveChartsCore.Measure.TooltipFindingStrategy.CompareOnlyX; - this.TooltipPosition = LiveChartsCore.Measure.TooltipPosition.Top; - - this.Series = new ISeries[]{ - new LineSeries{ - - Values = new List{ - new DateTimePoint(new DateTime(2024, 1, 7), 1985), - new DateTimePoint(new DateTime(2024, 1, 8), 3882), - new DateTimePoint(new DateTime(2024, 1, 11), 2511), - new DateTimePoint(new DateTime(2024, 1, 15), 3905), - new DateTimePoint(new DateTime(2024, 1, 20), 4256) - }, - Fill = null, - Name = "Balance", - YToolTipLabelFormatter = - (chartPoint) => $"{chartPoint.Context.Series.Name}: {chartPoint.StackedValue}" - } - }; - } - - public TooltipPosition TooltipPosition - { - get => this.tooltipPosition; - set => this.SetProperty(ref this.tooltipPosition, value); - } - - public List YAxes - { - get => this.yAxes; - set => this.SetProperty(ref this.yAxes, value); - } - - public List XAxes - { - get => this.xAxes; - set => this.SetProperty(ref this.xAxes, value); - } - - public ISeries[] Series - { - get => this.series; - set => this.SetProperty(ref this.series, value); - } - - public TooltipFindingStrategy TooltipFindingStrategy - { - get => this.tooltipFindingStrategy; - set => this.SetProperty(ref this.tooltipFindingStrategy, value); - } - - -} diff --git a/TransactionProcessor.Mobile.UITests/Features/HardwarePageNavigation.feature b/TransactionProcessor.Mobile.UITests/Features/HardwarePageNavigation.feature index a4d232139..2b087e2cf 100644 --- a/TransactionProcessor.Mobile.UITests/Features/HardwarePageNavigation.feature +++ b/TransactionProcessor.Mobile.UITests/Features/HardwarePageNavigation.feature @@ -48,10 +48,10 @@ Scenario: Back Button from Reports Page Screen Then the Merchant Home Page is displayed When I tap on Reports Then the Reports Page is displayed - When I tap on the Sales Analysis Button - Then the Sales Analysis Report is displayed + When I tap on the Daily Performance Summary Button + Then the Daily Performance Summary Report is displayed When I click on the device back button - Then the Transaction Page is displayed + Then the Reports Page is displayed When I click on the device back button Then the Merchant Home Page is displayed diff --git a/TransactionProcessor.Mobile.UITests/Features/HardwarePageNavigation.feature.cs b/TransactionProcessor.Mobile.UITests/Features/HardwarePageNavigation.feature.cs index da2b9f772..e09101b68 100644 --- a/TransactionProcessor.Mobile.UITests/Features/HardwarePageNavigation.feature.cs +++ b/TransactionProcessor.Mobile.UITests/Features/HardwarePageNavigation.feature.cs @@ -309,16 +309,16 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa await testRunner.ThenAsync("the Reports Page is displayed", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); #line hidden #line 51 - await testRunner.WhenAsync("I tap on the Sales Analysis Button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.WhenAsync("I tap on the Daily Performance Summary Button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); #line hidden #line 52 - await testRunner.ThenAsync("the Sales Analysis Report is displayed", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.ThenAsync("the Daily Performance Summary Report is displayed", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); #line hidden #line 53 await testRunner.WhenAsync("I click on the device back button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); #line hidden #line 54 - await testRunner.ThenAsync("the Transaction Page is displayed", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.ThenAsync("the Reports Page is displayed", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); #line hidden #line 55 await testRunner.WhenAsync("I click on the device back button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); diff --git a/TransactionProcessor.Mobile.UITests/Features/PageNavigation.feature b/TransactionProcessor.Mobile.UITests/Features/PageNavigation.feature index 004300db2..1da3875b4 100644 --- a/TransactionProcessor.Mobile.UITests/Features/PageNavigation.feature +++ b/TransactionProcessor.Mobile.UITests/Features/PageNavigation.feature @@ -54,8 +54,8 @@ Scenario: Back Button from Reports Page Screen Then the Merchant Home Page is displayed When I tap on Reports Then the Reports Page is displayed - When I tap on the Sales Analysis Button - Then the Sales Analysis Report is displayed + When I tap on the Daily Performance Summary Button + Then the Daily Performance Summary Report is displayed When I click on the back button Then the Reports Page is displayed When I click on the back button diff --git a/TransactionProcessor.Mobile.UITests/Features/PageNavigation.feature.cs b/TransactionProcessor.Mobile.UITests/Features/PageNavigation.feature.cs index 5d44407d0..7a750a6b9 100644 --- a/TransactionProcessor.Mobile.UITests/Features/PageNavigation.feature.cs +++ b/TransactionProcessor.Mobile.UITests/Features/PageNavigation.feature.cs @@ -344,10 +344,10 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa await testRunner.ThenAsync("the Reports Page is displayed", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); #line hidden #line 57 - await testRunner.WhenAsync("I tap on the Sales Analysis Button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.WhenAsync("I tap on the Daily Performance Summary Button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); #line hidden #line 58 - await testRunner.ThenAsync("the Sales Analysis Report is displayed", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.ThenAsync("the Daily Performance Summary Report is displayed", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); #line hidden #line 59 await testRunner.WhenAsync("I click on the back button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); diff --git a/TransactionProcessor.Mobile.UITests/Pages/DailyPerformanceSummaryPage.cs b/TransactionProcessor.Mobile.UITests/Pages/DailyPerformanceSummaryPage.cs new file mode 100644 index 000000000..2ba28dd33 --- /dev/null +++ b/TransactionProcessor.Mobile.UITests/Pages/DailyPerformanceSummaryPage.cs @@ -0,0 +1,12 @@ +using TransactionProcessor.Mobile.UITests.Common; + +namespace TransactionProcessor.Mobile.UITests.Pages; + +public class DailyPerformanceSummaryPage : BasePage2 +{ + protected override string Trait => "DailyPerformanceSummary"; + + public DailyPerformanceSummaryPage(TestingContext testingContext) : base(testingContext) + { + } +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs index 4617d5043..6fd94080a 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs @@ -18,27 +18,21 @@ protected override String Trait } } - private readonly String SalesAnalysisButton; - private readonly String BalanceAnalysisButton; + private readonly String DailyPerformanceSummaryButton; public ReportsPage(TestingContext testingContext) : base(testingContext) { - this.SalesAnalysisButton = "SalesAnalysisButton"; - this.BalanceAnalysisButton = "BalanceAnalysisButton"; - } - public async Task ClickBalanceAnalysisButton() - { - IWebElement element = await this.WaitForElementByAccessibilityId(this.BalanceAnalysisButton); - element.Click(); + this.DailyPerformanceSummaryButton = "DailyPerformanceSummaryButton"; } - public async Task ClickSalesAnalysisButton() + public async Task ClickDailyPerformanceSummaryButton() { - IWebElement element = await this.WaitForElementByAccessibilityId(this.SalesAnalysisButton); + IWebElement element = await this.WaitForElementByAccessibilityId(this.DailyPerformanceSummaryButton); element.Click(); } + } public class SupportPage : BasePage2{ @@ -72,4 +66,4 @@ public async Task ClickViewLogsButton() IWebElement element = await this.WaitForElementByAccessibilityId(this.ViewLogsButton); element.Click(); } -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Steps/ReportsSteps.cs b/TransactionProcessor.Mobile.UITests/Steps/ReportsSteps.cs index fb3a52cb5..9324e2d8b 100644 --- a/TransactionProcessor.Mobile.UITests/Steps/ReportsSteps.cs +++ b/TransactionProcessor.Mobile.UITests/Steps/ReportsSteps.cs @@ -7,12 +7,11 @@ namespace TransactionProcessor.Mobile.UITests.Steps; [Scope(Tag = "reports")] public class ReportsSteps{ private readonly ReportsPage ReportsPage; + private readonly DailyPerformanceSummaryPage DailyPerformanceSummaryPage; - private readonly SalesAnalysisPage SalesAnalysisPage; - - public ReportsSteps(ReportsPage reportsPage, SalesAnalysisPage salesAnalysisPage){ + public ReportsSteps(ReportsPage reportsPage, DailyPerformanceSummaryPage dailyPerformanceSummaryPage){ this.ReportsPage = reportsPage; - this.SalesAnalysisPage = salesAnalysisPage; + this.DailyPerformanceSummaryPage = dailyPerformanceSummaryPage; } [Then(@"the Reports Page is displayed")] @@ -21,16 +20,16 @@ public async Task ThenTheReportsPageIsDisplayed() await this.ReportsPage.AssertOnPage(); } - [When(@"I tap on the Sales Analysis Button")] - public async Task WhenITapOnTheSalesAnalysisButton(){ - await this.ReportsPage.ClickSalesAnalysisButton(); + [When(@"I tap on the Daily Performance Summary Button")] + public async Task WhenITapOnTheDailyPerformanceSummaryButton() + { + await this.ReportsPage.ClickDailyPerformanceSummaryButton(); } - [Then(@"the Sales Analysis Report is displayed")] - public async Task ThenTheSalesAnalysisReportIsDisplayed() + [Then(@"the Daily Performance Summary Report is displayed")] + public async Task ThenTheDailyPerformanceSummaryReportIsDisplayed() { - await this.SalesAnalysisPage.AssertOnPage(); + await this.DailyPerformanceSummaryPage.AssertOnPage(); } - -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile/App.xaml.cs b/TransactionProcessor.Mobile/App.xaml.cs index 51a6ef0a5..76caa6aa1 100644 --- a/TransactionProcessor.Mobile/App.xaml.cs +++ b/TransactionProcessor.Mobile/App.xaml.cs @@ -177,8 +177,7 @@ public App(IApplicationThemeService applicationThemeService) RegisterRouteOnce(nameof(ViewLogsPage), typeof(ViewLogsPage)); - RegisterRouteOnce(nameof(ReportsBalanceAnalysisPage), typeof(ReportsBalanceAnalysisPage)); - RegisterRouteOnce(nameof(ReportsSalesAnalysisPage), typeof(ReportsSalesAnalysisPage)); + RegisterRouteOnce(nameof(DailyPerformanceSummaryPage), typeof(DailyPerformanceSummaryPage)); RegisterRouteOnce(nameof(MyAccountAddressesPage), typeof(MyAccountAddressesPage)); RegisterRouteOnce(nameof(MyAccountContactPage), typeof(MyAccountContactPage)); diff --git a/TransactionProcessor.Mobile/Extensions/MauiAppBuilderExtensions.cs b/TransactionProcessor.Mobile/Extensions/MauiAppBuilderExtensions.cs index eb915b531..44591f0f5 100644 --- a/TransactionProcessor.Mobile/Extensions/MauiAppBuilderExtensions.cs +++ b/TransactionProcessor.Mobile/Extensions/MauiAppBuilderExtensions.cs @@ -226,8 +226,7 @@ public static MauiAppBuilder ConfigureViewModels(this MauiAppBuilder builder) { builder.Services.AddTransient(); builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); + builder.Services.AddTransient(); return builder; } @@ -268,8 +267,7 @@ public static MauiAppBuilder ConfigurePages(this MauiAppBuilder builder) { builder.Services.AddTransient(); builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); + builder.Services.AddTransient(); return builder; } diff --git a/TransactionProcessor.Mobile/Pages/Reports/DailyPerformanceSummaryPage.xaml b/TransactionProcessor.Mobile/Pages/Reports/DailyPerformanceSummaryPage.xaml new file mode 100644 index 000000000..4d0b46aea --- /dev/null +++ b/TransactionProcessor.Mobile/Pages/Reports/DailyPerformanceSummaryPage.xaml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +