diff --git a/src/Application/Features/Dashboard/DTOs/ActivityLogDto.cs b/src/Application/Features/Dashboard/DTOs/ActivityLogDto.cs new file mode 100644 index 00000000..0c00036c --- /dev/null +++ b/src/Application/Features/Dashboard/DTOs/ActivityLogDto.cs @@ -0,0 +1,37 @@ +using Cfo.Cats.Domain.Entities.Participants; + +namespace Cfo.Cats.Application.Features.Dashboard.DTOs; + +public class ActivityLogDto +{ + public string ParticipantId { get; set; } = default!; + public string ParticipantName { get;set; } = default!; + + public TimelineEventType EventType { get;set; } = default!; + + public string Line1 { get;set; } = default!; + public string? Line2 {get;set;} + public string? Line3 {get;set;} + public string CreatedBy {get;set;} = default!; + public DateTime Created { get; set;} + + private class Mapper : Profile + { + public Mapper() + { + #nullable disable + CreateMap(MemberList.None) + .ForMember(t => t.ParticipantId, o => o.MapFrom(s => s.ParticipantId)) + .ForMember(t => t.ParticipantName, o => o.MapFrom(s => s.Participant.FirstName + " " + s.Participant.LastName)) + .ForMember(t => t.EventType, o => o.MapFrom(s => s.EventType)) + .ForMember(t => t.Line1, o => o.MapFrom(s => s.Line1)) + .ForMember(t => t.Line2, o => o.MapFrom(s => s.Line2)) + .ForMember(t => t.Line3, o => o.MapFrom(s => s.Line3)) + .ForMember(t => t.CreatedBy, o => o.MapFrom(s => s.CreatedByUser.DisplayName)) + .ForMember(t => t.Created, o => o.MapFrom(s => s.Created)); + + } + } + + +} \ No newline at end of file diff --git a/src/Application/Features/Dashboard/Queries/GetActivityLog.cs b/src/Application/Features/Dashboard/Queries/GetActivityLog.cs new file mode 100644 index 00000000..1a3e91b3 --- /dev/null +++ b/src/Application/Features/Dashboard/Queries/GetActivityLog.cs @@ -0,0 +1,41 @@ +using Cfo.Cats.Application.Common.Security; +using Cfo.Cats.Application.Features.Dashboard.DTOs; +using Cfo.Cats.Application.Features.Dashboard.Specifications; +using Cfo.Cats.Application.SecurityConstants; +using Cfo.Cats.Domain.Entities.Participants; +using DocumentFormat.OpenXml.Vml.Spreadsheet; + +namespace Cfo.Cats.Application.Features.Dashboard.Queries; + +public static class GetActivityLog +{ + [RequestAuthorize(Policy = SecurityPolicies.AuthorizedUser)] + public class Query : ActivityLogFilter, IRequest> + { + public ActivityLogSpecification Specification => new(this); + + } + + public class Handler(IUnitOfWork unitOfWork, IMapper mapper) : IRequestHandler> + { + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var data = await unitOfWork.DbContext.Timelines.AsNoTracking() + .OrderBy($"{request.OrderBy} {request.SortDirection}") + .ProjectToPaginatedDataAsync(request.Specification, request.PageNumber, request.PageSize, mapper.ConfigurationProvider, cancellationToken ); + + return data!; + } + } + + public class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.CurrentUser) + .NotNull(); + } + } + + +} diff --git a/src/Application/Features/Dashboard/Specifications/ActivityLogFilter.cs b/src/Application/Features/Dashboard/Specifications/ActivityLogFilter.cs new file mode 100644 index 00000000..69e7ebad --- /dev/null +++ b/src/Application/Features/Dashboard/Specifications/ActivityLogFilter.cs @@ -0,0 +1,9 @@ +using Cfo.Cats.Application.Common.Security; + +namespace Cfo.Cats.Application.Features.Dashboard.Specifications; + +public class ActivityLogFilter : PaginationFilter +{ + public UserProfile? CurrentUser { get;set; } + public ActivityLogListView ListView { get; set; } = ActivityLogListView.All; +} \ No newline at end of file diff --git a/src/Application/Features/Dashboard/Specifications/ActivityLogListView.cs b/src/Application/Features/Dashboard/Specifications/ActivityLogListView.cs new file mode 100644 index 00000000..bf33bad2 --- /dev/null +++ b/src/Application/Features/Dashboard/Specifications/ActivityLogListView.cs @@ -0,0 +1,9 @@ +namespace Cfo.Cats.Application.Features.Dashboard.Specifications; + +public enum ActivityLogListView +{ + [Description("All")] + All, + [Description("Created Today")] + CreatedToday, +} diff --git a/src/Application/Features/Dashboard/Specifications/ActivityLogSpecification.cs b/src/Application/Features/Dashboard/Specifications/ActivityLogSpecification.cs new file mode 100644 index 00000000..48536e4c --- /dev/null +++ b/src/Application/Features/Dashboard/Specifications/ActivityLogSpecification.cs @@ -0,0 +1,24 @@ +using Cfo.Cats.Domain.Entities.Participants; + +namespace Cfo.Cats.Application.Features.Dashboard.Specifications; + +public class ActivityLogSpecification : Specification +{ + public ActivityLogSpecification(ActivityLogFilter filter) + { + #nullable disable + + var today = DateTime.Now.ToUniversalTime().Date; + + + + Query.Where(t => t.Participant.OwnerId == filter.CurrentUser.UserId, filter.CurrentUser.AssignedRoles is []); + + Query.Where(t => t.Participant.Owner.TenantId.StartsWith(filter.CurrentUser.TenantId)); + + Query.Where( + p => p.Created >= DateTime.Now.Date, + filter.ListView == ActivityLogListView.CreatedToday + ); + } +} diff --git a/src/Domain/Entities/Participants/Timeline.cs b/src/Domain/Entities/Participants/Timeline.cs index 26e134c5..06102340 100644 --- a/src/Domain/Entities/Participants/Timeline.cs +++ b/src/Domain/Entities/Participants/Timeline.cs @@ -37,5 +37,7 @@ public static Timeline CreateTimeline(string participantId, TimelineEventType ev } public virtual ApplicationUser? CreatedByUser { get; private set; } + + public virtual Participant? Participant { get; private set; } } diff --git a/src/Infrastructure/Persistence/Configurations/Participants/TimelineEntityTypeConfiguration.cs b/src/Infrastructure/Persistence/Configurations/Participants/TimelineEntityTypeConfiguration.cs index ff8b43d4..b89a2ccb 100644 --- a/src/Infrastructure/Persistence/Configurations/Participants/TimelineEntityTypeConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/Participants/TimelineEntityTypeConfiguration.cs @@ -16,7 +16,7 @@ public void Configure(EntityTypeBuilder builder) builder.HasKey(t => t.Id); - builder.HasOne() + builder.HasOne(t => t.Participant) .WithMany() .HasForeignKey("ParticipantId"); diff --git a/src/Server.UI/Pages/Dashboard/Components/ActivityLog.razor b/src/Server.UI/Pages/Dashboard/Components/ActivityLog.razor new file mode 100644 index 00000000..07fc28f1 --- /dev/null +++ b/src/Server.UI/Pages/Dashboard/Components/ActivityLog.razor @@ -0,0 +1,147 @@ +@using Cfo.Cats.Application.Features.Dashboard.DTOs +@using Cfo.Cats.Application.Features.Dashboard.Specifications +@using Cfo.Cats.Application.Features.Dashboard.Queries +@using Humanizer + + +@inherits CatsComponentBase + + + + + Activity Log + + + + + +
+
+ +
+ Activity Log + + +
+
+
+ +
+
+ + @ConstantString.Refresh + +
+
+
+ + + + + + + +
+ @context.Item.Line1 + @if (context.Item.Line2 is not null) + { + @context.Item.Line2 + } + @if (context.Item.Line3 is not null) + { + @context.Item.Line3 + } +
+
+
+ + +
+ @context.Item.CreatedBy + @context.Item.Created.Humanize() + +
+
+
+
+ + @ConstantString.NoRecords + + + @ConstantString.Loading + + + + + + + + +@code +{ + [CascadingParameter] + public UserProfile? UserProfile { get; set; } + + private GetActivityLog.Query Query { get; } = new(); + + private MudDataGrid table = null!; + + private bool loading; + + private int defaultPageSize = 15; + + private readonly ActivityLogDto currentDto = new(); + + private async Task> ServerReload(GridState state) + { + try + { + loading = true; + Query.CurrentUser = UserProfile; + Query.OrderBy = state.SortDefinitions.FirstOrDefault()?.SortBy ?? "Created"; + Query.SortDirection = state.SortDefinitions.FirstOrDefault()?.Descending ?? true ? SortDirection.Descending.ToString() : + SortDirection.Ascending.ToString(); + Query.PageNumber = state.Page + 1; + Query.PageSize = state.PageSize; + + var result = await GetNewMediator().Send(Query).ConfigureAwait(false); + return new GridData { TotalItems = result.TotalItems, Items = result.Items }; + } + finally + { + loading = false; + } + } + + private async Task OnRefresh() + { + await table.ReloadServerData(); + } + + private async Task OnChangedListView(ActivityLogListView listview) + { + Query.ListView = listview; + await table.ReloadServerData(); + } + + private void RowClicked(DataGridRowClickEventArgs args) + { + Navigation.NavigateTo($"/pages/participants/{args.Item.ParticipantId}"); + } + +} diff --git a/src/Server.UI/Pages/Dashboard/Dashboard.razor b/src/Server.UI/Pages/Dashboard/Dashboard.razor index a10a1fe3..3c5a980d 100644 --- a/src/Server.UI/Pages/Dashboard/Dashboard.razor +++ b/src/Server.UI/Pages/Dashboard/Dashboard.razor @@ -1,6 +1,10 @@ @page "/" +@using Cfo.Cats.Application.Features.Dashboard.DTOs @using Cfo.Cats.Application.Features.Dashboard.Queries @using Cfo.Cats.Application.SecurityConstants +@using Cfo.Cats.Domain.Common.Enums +@using Cfo.Cats.Server.UI.Pages.Dashboard.Components +@using Humanizer @inherits CatsComponentBase @@ -13,90 +17,97 @@ Dashboard - - - - - - Pending Cases + + @if (UserProfile?.AssignedRoles is []) + { + + + + + + Pending Cases + + + + + Your cases that are awaiting enrolment confirmation + + @_dashboardDto?.PendingCases - - - - Your cases that are awaiting enrolment confirmation - - @_dashboardDto?.PendingCases - - - - - - - - - - Enrolling Cases + + + + + + + + + Enrolling Cases + + + + + Your cases that are awaiting submission to PQA + + @_dashboardDto?.ConfirmedCases - - - - Your cases that are awaiting submission to PQA - - @_dashboardDto?.ConfirmedCases - - - - - - - - - - Provider QA + + + + + + + + + Provider QA + + + + + Your cases that are sat with Provider QA + + @_dashboardDto?.CasesAtPqa - - - - Your cases that are sat with Provider QA - - @_dashboardDto?.CasesAtPqa - - - - - - - - - - CFO QA + + + + + + + + + CFO QA + + + + + Your cases that are sat with CFO QA + + @_dashboardDto?.CasesAtCfo - - - - Your cases that are sat with CFO QA - - @_dashboardDto?.CasesAtCfo - - - - - - - - - - Approved Cases + + + + + + + + + Approved Cases + + + + + Your cases that have been approved + + @_dashboardDto?.ApprovedCases - - - - Your cases that have been approved - - @_dashboardDto?.ApprovedCases - - - + + + + } + + @@ -107,17 +118,19 @@ public string Title { get; } = "Dashboard"; - private DashboardDto? _dashboardDto = null; - + private DashboardDto? _dashboardDto = null; + + + [CascadingParameter] public UserProfile? UserProfile { get; set; } protected override async Task OnInitializedAsync() { var query = new GetDashboard.Query() - { - CurrentUser = UserProfile! - }; + { + CurrentUser = UserProfile! + }; var result = await GetNewMediator().Send(query); @@ -125,7 +138,11 @@ { _dashboardDto = result.Data; } - + + + } + + }