From b02e0afe018064800d0e2c067d0efc7d699d8434 Mon Sep 17 00:00:00 2001 From: Carl Sixsmith Date: Wed, 10 Jul 2024 13:52:23 +0100 Subject: [PATCH] Work on showing the assessment in the participant dashboard. This had to push into some caching stuff due to the new ParticipantSummaryDto lightweight type. --- .../Caching/ICacheInvalidatorRequest.cs | 2 +- .../Assessments/Commands/BeginAssessment.cs | 2 +- .../Assessments/Commands/SaveAssessment.cs | 2 +- .../Candidates/Caching/CandidatesCacheKey.cs | 2 +- .../Queries/Search/CandidateSearchQuery.cs | 2 +- .../Commands/Upload/UploadDocumentCommand.cs | 2 +- .../AddEdit/AddEditKeyValueCommand.cs | 3 +- .../Commands/Delete/DeleteKeyValueCommand.cs | 2 +- .../Commands/Import/ImportKeyValuesCommand.cs | 2 +- .../Caching/ParticipantCacheKey.cs | 1 + .../Commands/CreateParticipant.cs | 2 +- .../Commands/SetEnrolmentLocation.cs | 2 +- .../Transistion/SubmitToProviderQa.cs | 8 +- .../Participants/DTOs/ParticipantDto.cs | 1 + .../DTOs/ParticipantSummaryDto.cs | 85 +++++++++++++++++ .../Queries/GetParticipantSummary.cs | 58 ++++++++++++ .../Commands/AddEdit/AddEditTenantCommand.cs | 2 +- .../Commands/Delete/DeleteTenantCommand.cs | 2 +- .../Tenants/Commands/Rename/RenameTenant.cs | 2 +- .../Pipeline/CacheInvalidationBehaviour.cs | 5 +- src/Domain/Common/Enums/EnrolmentStatus.cs | 9 ++ .../Constants/ConstantString.cs | 3 +- .../Pages/Assessment/Assessment.razor | 2 +- .../Participants/Components/CaseSummary.razor | 91 +++++++++++++------ .../Components/CaseSummary.razor.cs | 73 +++++++++++++++ .../Pages/Participants/Participant.razor | 20 ++-- .../Pages/Participants/Participants.razor | 45 ++++++--- .../Services/Navigation/MenuService.cs | 1 - src/Server.UI/wwwroot/css/app.css | 23 +++++ 29 files changed, 384 insertions(+), 70 deletions(-) rename src/Application/Features/{Documents => Participants}/Commands/Transistion/SubmitToProviderQa.cs (77%) create mode 100644 src/Application/Features/Participants/DTOs/ParticipantSummaryDto.cs create mode 100644 src/Application/Features/Participants/Queries/GetParticipantSummary.cs create mode 100644 src/Server.UI/Pages/Participants/Components/CaseSummary.razor.cs diff --git a/src/Application/Common/Interfaces/Caching/ICacheInvalidatorRequest.cs b/src/Application/Common/Interfaces/Caching/ICacheInvalidatorRequest.cs index ca58686a..502fe223 100644 --- a/src/Application/Common/Interfaces/Caching/ICacheInvalidatorRequest.cs +++ b/src/Application/Common/Interfaces/Caching/ICacheInvalidatorRequest.cs @@ -2,7 +2,7 @@ public interface ICacheInvalidatorRequest : IRequest { - string CacheKey { get; } + string[] CacheKeys { get; } CancellationTokenSource? SharedExpiryTokenSource { get; } } diff --git a/src/Application/Features/Assessments/Commands/BeginAssessment.cs b/src/Application/Features/Assessments/Commands/BeginAssessment.cs index 819800ff..2fe16832 100644 --- a/src/Application/Features/Assessments/Commands/BeginAssessment.cs +++ b/src/Application/Features/Assessments/Commands/BeginAssessment.cs @@ -24,7 +24,7 @@ public class Command : ICacheInvalidatorRequest> public required string ParticipantId { get; set; } //TODO: this could be done at a per participant level - public string CacheKey => AssessmentsCacheKey.GetAllCacheKey; + public string[] CacheKeys => [ AssessmentsCacheKey.GetAllCacheKey ]; public CancellationTokenSource? SharedExpiryTokenSource => AssessmentsCacheKey.SharedExpiryTokenSource(); } diff --git a/src/Application/Features/Assessments/Commands/SaveAssessment.cs b/src/Application/Features/Assessments/Commands/SaveAssessment.cs index d04de9e4..93cd9459 100644 --- a/src/Application/Features/Assessments/Commands/SaveAssessment.cs +++ b/src/Application/Features/Assessments/Commands/SaveAssessment.cs @@ -13,7 +13,7 @@ public static class SaveAssessment public class Command : ICacheInvalidatorRequest { //TODO: cache individually - public string CacheKey => AssessmentsCacheKey.GetAllCacheKey; + public string[] CacheKeys => [ AssessmentsCacheKey.GetAllCacheKey ]; public CancellationTokenSource? SharedExpiryTokenSource => AssessmentsCacheKey.SharedExpiryTokenSource(); public bool Submit { get; set; } = false; diff --git a/src/Application/Features/Candidates/Caching/CandidatesCacheKey.cs b/src/Application/Features/Candidates/Caching/CandidatesCacheKey.cs index ded77e24..c870d5f1 100644 --- a/src/Application/Features/Candidates/Caching/CandidatesCacheKey.cs +++ b/src/Application/Features/Candidates/Caching/CandidatesCacheKey.cs @@ -20,7 +20,7 @@ public static CancellationTokenSource SharedExpiryTokenSource() return tokenSource; } - public static string GetCacheKey(string parameters) + public static string GetCandidateCacheKey(string parameters) { return $"EnrolmentsWithPaginationQuery,{parameters}"; } diff --git a/src/Application/Features/Candidates/Queries/Search/CandidateSearchQuery.cs b/src/Application/Features/Candidates/Queries/Search/CandidateSearchQuery.cs index 076f5f52..931aa3a5 100644 --- a/src/Application/Features/Candidates/Queries/Search/CandidateSearchQuery.cs +++ b/src/Application/Features/Candidates/Queries/Search/CandidateSearchQuery.cs @@ -14,7 +14,7 @@ public class CandidateSearchQuery : ICacheableRequest> public required DateTime? DateOfBirth { get; set; } public required UserProfile CurrentUser { get; set; } - public string CacheKey => CandidatesCacheKey.GetCacheKey($"{this}"); + public string CacheKey => CandidatesCacheKey.GetCandidateCacheKey($"{this}"); public MemoryCacheEntryOptions? Options => CandidatesCacheKey.MemoryCacheEntryOptions; diff --git a/src/Application/Features/Documents/Commands/Upload/UploadDocumentCommand.cs b/src/Application/Features/Documents/Commands/Upload/UploadDocumentCommand.cs index b461227f..267a37b2 100644 --- a/src/Application/Features/Documents/Commands/Upload/UploadDocumentCommand.cs +++ b/src/Application/Features/Documents/Commands/Upload/UploadDocumentCommand.cs @@ -7,7 +7,7 @@ namespace Cfo.Cats.Application.Features.Documents.Commands.Upload; [RequestAuthorize(Policy = PolicyNames.AllowDocumentUpload)] public class UploadDocumentCommand : ICacheInvalidatorRequest> { - public string CacheKey { get; } = string.Empty; + public string[] CacheKeys { get; } = []; public CancellationTokenSource? SharedExpiryTokenSource => DocumentCacheKey.SharedExpiryTokenSource(); diff --git a/src/Application/Features/KeyValues/Commands/AddEdit/AddEditKeyValueCommand.cs b/src/Application/Features/KeyValues/Commands/AddEdit/AddEditKeyValueCommand.cs index 8ce8fbdc..70e4beb6 100644 --- a/src/Application/Features/KeyValues/Commands/AddEdit/AddEditKeyValueCommand.cs +++ b/src/Application/Features/KeyValues/Commands/AddEdit/AddEditKeyValueCommand.cs @@ -19,7 +19,8 @@ public class AddEditKeyValueCommand : ICacheInvalidatorRequest> [Description("Description")] public string? Description { get; set; } public TrackingState TrackingState { get; set; } = TrackingState.Unchanged; - public string CacheKey => KeyValueCacheKey.GetAllCacheKey; + public string[] CacheKeys => [KeyValueCacheKey.GetAllCacheKey]; + public CancellationTokenSource? SharedExpiryTokenSource => KeyValueCacheKey.SharedExpiryTokenSource(); private class Mapping : Profile diff --git a/src/Application/Features/KeyValues/Commands/Delete/DeleteKeyValueCommand.cs b/src/Application/Features/KeyValues/Commands/Delete/DeleteKeyValueCommand.cs index a5d4a88b..788b1524 100644 --- a/src/Application/Features/KeyValues/Commands/Delete/DeleteKeyValueCommand.cs +++ b/src/Application/Features/KeyValues/Commands/Delete/DeleteKeyValueCommand.cs @@ -12,6 +12,6 @@ public DeleteKeyValueCommand(int[] id) } public int[] Id { get; } - public string CacheKey => KeyValueCacheKey.GetAllCacheKey; + public string[] CacheKeys => [ KeyValueCacheKey.GetAllCacheKey ]; public CancellationTokenSource? SharedExpiryTokenSource => KeyValueCacheKey.SharedExpiryTokenSource(); } \ No newline at end of file diff --git a/src/Application/Features/KeyValues/Commands/Import/ImportKeyValuesCommand.cs b/src/Application/Features/KeyValues/Commands/Import/ImportKeyValuesCommand.cs index a472c7df..61d125dc 100644 --- a/src/Application/Features/KeyValues/Commands/Import/ImportKeyValuesCommand.cs +++ b/src/Application/Features/KeyValues/Commands/Import/ImportKeyValuesCommand.cs @@ -8,6 +8,6 @@ public class ImportKeyValuesCommand(string fileName, byte[] data) : ICacheInvali { public string FileName { get; set; } = fileName; public byte[] Data { get; set; } = data; - public string CacheKey => KeyValueCacheKey.GetAllCacheKey; + public string[] CacheKeys => [ KeyValueCacheKey.GetAllCacheKey ]; public CancellationTokenSource? SharedExpiryTokenSource => KeyValueCacheKey.SharedExpiryTokenSource(); } \ No newline at end of file diff --git a/src/Application/Features/Participants/Caching/ParticipantCacheKey.cs b/src/Application/Features/Participants/Caching/ParticipantCacheKey.cs index 845ddb19..821033d6 100644 --- a/src/Application/Features/Participants/Caching/ParticipantCacheKey.cs +++ b/src/Application/Features/Participants/Caching/ParticipantCacheKey.cs @@ -16,6 +16,7 @@ static ParticipantCacheKey() ); public static string GetCacheKey(string parameters) => $"ParticipantCacheKey,{parameters}"; + public static string GetSummaryCacheKey(string id) => $"ParticipantSummary,{id}"; public static CancellationTokenSource SharedExpiryTokenSource() { diff --git a/src/Application/Features/Participants/Commands/CreateParticipant.cs b/src/Application/Features/Participants/Commands/CreateParticipant.cs index 9a6b4735..57e58490 100644 --- a/src/Application/Features/Participants/Commands/CreateParticipant.cs +++ b/src/Application/Features/Participants/Commands/CreateParticipant.cs @@ -24,7 +24,7 @@ public class Command: ICacheInvalidatorRequest> public UserProfile? CurrentUser { get; set; } - public string CacheKey => ParticipantCacheKey.GetCacheKey($"{this}"); + public string[] CacheKeys => [ ParticipantCacheKey.GetCacheKey($"{this}") ]; public CancellationTokenSource? SharedExpiryTokenSource => ParticipantCacheKey.SharedExpiryTokenSource(); diff --git a/src/Application/Features/Participants/Commands/SetEnrolmentLocation.cs b/src/Application/Features/Participants/Commands/SetEnrolmentLocation.cs index f7c1e9de..6b46f1eb 100644 --- a/src/Application/Features/Participants/Commands/SetEnrolmentLocation.cs +++ b/src/Application/Features/Participants/Commands/SetEnrolmentLocation.cs @@ -39,7 +39,7 @@ public class Command(string identifier, LocationDto currentLocation, LocationDto [Description("Enrol at an alternative location enrolment")] public bool EnrolFromOtherLocation { get; set; } = enrolmentLocation.Id != currentLocation.Id; - public string CacheKey => ParticipantCacheKey.GetCacheKey($"Id:{this.Identifier}"); + public string[] CacheKeys => [ParticipantCacheKey.GetCacheKey($"Id:{this.Identifier}")]; public CancellationTokenSource? SharedExpiryTokenSource => ParticipantCacheKey.SharedExpiryTokenSource(); } diff --git a/src/Application/Features/Documents/Commands/Transistion/SubmitToProviderQa.cs b/src/Application/Features/Participants/Commands/Transistion/SubmitToProviderQa.cs similarity index 77% rename from src/Application/Features/Documents/Commands/Transistion/SubmitToProviderQa.cs rename to src/Application/Features/Participants/Commands/Transistion/SubmitToProviderQa.cs index e244a04a..ad54b9a8 100644 --- a/src/Application/Features/Documents/Commands/Transistion/SubmitToProviderQa.cs +++ b/src/Application/Features/Participants/Commands/Transistion/SubmitToProviderQa.cs @@ -1,8 +1,9 @@ using Cfo.Cats.Application.Common.Security; using Cfo.Cats.Application.Features.Candidates.Caching; +using Cfo.Cats.Application.Features.Participants.Caching; using Cfo.Cats.Application.SecurityConstants; -namespace Cfo.Cats.Application.Features.Documents.Commands.Transistion; +namespace Cfo.Cats.Application.Features.Participants.Commands.Transistion; public static class SubmitToProviderQa { @@ -11,7 +12,10 @@ public class Command : ICacheInvalidatorRequest { public required string ParticipantId { get; set; } - public string CacheKey => CandidatesCacheKey.GetCacheKey(ParticipantId); + public string[] CacheKeys => [ + ParticipantCacheKey.GetCacheKey(ParticipantId), + ParticipantCacheKey.GetSummaryCacheKey(ParticipantId) + ]; public CancellationTokenSource? SharedExpiryTokenSource => CandidatesCacheKey.SharedExpiryTokenSource(); } diff --git a/src/Application/Features/Participants/DTOs/ParticipantDto.cs b/src/Application/Features/Participants/DTOs/ParticipantDto.cs index af4f6de4..3ead047b 100644 --- a/src/Application/Features/Participants/DTOs/ParticipantDto.cs +++ b/src/Application/Features/Participants/DTOs/ParticipantDto.cs @@ -3,6 +3,7 @@ namespace Cfo.Cats.Application.Features.Participants.DTOs; +[Description("Participants")] public class ParticipantDto { [Description("CATS Identifier")] diff --git a/src/Application/Features/Participants/DTOs/ParticipantSummaryDto.cs b/src/Application/Features/Participants/DTOs/ParticipantSummaryDto.cs new file mode 100644 index 00000000..192e8eac --- /dev/null +++ b/src/Application/Features/Participants/DTOs/ParticipantSummaryDto.cs @@ -0,0 +1,85 @@ +using Cfo.Cats.Domain.Entities.Assessments; +using Cfo.Cats.Domain.Entities.Participants; + +namespace Cfo.Cats.Application.Features.Participants.DTOs; + +/// +/// Represents the initial dashboard +/// +public class ParticipantSummaryDto +{ + + public required string Id { get; set; } + + /// + /// The full name of the participant + /// + public required string ParticipantName { get; set; } + + /// + /// The current location of the participant + /// + public required string Location { get; set; } + + /// + /// The participant's date of birth + /// + public required DateOnly DateOfBirth { get; set; } + + /// + /// The current enrolment status of the participant + /// + public EnrolmentStatus EnrolmentStatus { get; set; } = EnrolmentStatus.PendingStatus; + + /// + /// The person who "owns" this participant's case. Usually the support worker. + /// + public required string OwnerName { get; set; } + + public AssessmentSummaryDto[] Assessments { get; set; } = []; + + + private class Mapping : Profile + { + public Mapping() + { + CreateMap(MemberList.None) + .ForMember(target => target.Id, options => options.MapFrom(source => source.Id)) + .ForMember(target => target.Location, options => options.MapFrom(source => source.CurrentLocation.Name)) + .ForMember(target => target.OwnerName, options => options.MapFrom(source => source.Owner!.DisplayName)); + + CreateMap() + .ForMember(target => target.AssessmentId, options => options.MapFrom(source => source.Id)) + .ForMember(target => target.AssessmentDate, options => options.MapFrom(source => source.Created)) + .ForMember(target => target.AssessmentCreator, options => options.MapFrom(source => source.CreatedBy)) + .ForMember(target => target.AssessmentScored, options => options.MapFrom(source => source.Scores.All(s => s.Score >= 0))); + } + } + + +} + +public class AssessmentSummaryDto +{ + /// + /// The id of the latest assessment + /// + public Guid? AssessmentId { get; set; } + + + /// + /// If there are any assessments these are the dates they latest one was created. + /// + public DateTime? AssessmentDate { get; set; } + + /// + /// Who created the most recent assessment (if available) + /// + public string? AssessmentCreator { get; set; } + + /// + /// Has the latest assessment been scored? This can be a surrogate for + /// submitted and should make the assessment read-only + /// + public bool? AssessmentScored { get; set; } +} diff --git a/src/Application/Features/Participants/Queries/GetParticipantSummary.cs b/src/Application/Features/Participants/Queries/GetParticipantSummary.cs new file mode 100644 index 00000000..36096810 --- /dev/null +++ b/src/Application/Features/Participants/Queries/GetParticipantSummary.cs @@ -0,0 +1,58 @@ +using Cfo.Cats.Application.Common.Security; +using Cfo.Cats.Application.Features.Participants.Caching; +using Cfo.Cats.Application.Features.Participants.DTOs; +using Cfo.Cats.Application.SecurityConstants; + +namespace Cfo.Cats.Application.Features.Participants.Queries; + +public static class GetParticipantSummary +{ + [RequestAuthorize(Policy = PolicyNames.AllowCandidateSearch)] + public class Query : ICacheableRequest> + { + public required string ParticipantId { get; set; } + public required UserProfile CurrentUser { get; set; } + + public string CacheKey => ParticipantCacheKey.GetCacheKey($"ParticipantSummary,{ParticipantId}"); + public MemoryCacheEntryOptions? Options => ParticipantCacheKey.MemoryCacheEntryOptions; + + } + + public class Handler(IApplicationDbContext context, IMapper mapper) : IRequestHandler> + { + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var query = from c in context.Participants + where c.Id == request.ParticipantId + select c; + + var summary = await query.ProjectTo(mapper.ConfigurationProvider) + .FirstOrDefaultAsync(cancellationToken); + + if (summary == null) + { + throw new NotFoundException(nameof(ParticipantSummaryDto), request.ParticipantId); + } + + summary.Assessments = await context.ParticipantAssessments + .Where(pa => pa.ParticipantId == request.ParticipantId) + .ProjectTo(mapper.ConfigurationProvider) + .ToArrayAsync(cancellationToken); + + return await Result.SuccessAsync(summary); + + } + } + + public class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.ParticipantId) + .MinimumLength(9) + .MaximumLength(9) + .WithMessage("Invalid participant id"); + } + } +} \ No newline at end of file diff --git a/src/Application/Features/Tenants/Commands/AddEdit/AddEditTenantCommand.cs b/src/Application/Features/Tenants/Commands/AddEdit/AddEditTenantCommand.cs index 4b94ce90..50361232 100644 --- a/src/Application/Features/Tenants/Commands/AddEdit/AddEditTenantCommand.cs +++ b/src/Application/Features/Tenants/Commands/AddEdit/AddEditTenantCommand.cs @@ -18,7 +18,7 @@ public class AddEditTenantCommand : ICacheInvalidatorRequest> [Description("Description")] public string? Description { get; set; } - public string CacheKey => TenantCacheKey.GetAllCacheKey; + public string[] CacheKeys => [TenantCacheKey.GetAllCacheKey]; public CancellationTokenSource? SharedExpiryTokenSource => TenantCacheKey.SharedExpiryTokenSource(); diff --git a/src/Application/Features/Tenants/Commands/Delete/DeleteTenantCommand.cs b/src/Application/Features/Tenants/Commands/Delete/DeleteTenantCommand.cs index 4a18bb9a..946b961c 100644 --- a/src/Application/Features/Tenants/Commands/Delete/DeleteTenantCommand.cs +++ b/src/Application/Features/Tenants/Commands/Delete/DeleteTenantCommand.cs @@ -13,7 +13,7 @@ public DeleteTenantCommand(string[] id) } public string[] Id { get; } - public string CacheKey => TenantCacheKey.GetAllCacheKey; + public string[] CacheKeys => [TenantCacheKey.GetAllCacheKey]; public CancellationTokenSource? SharedExpiryTokenSource => TenantCacheKey.SharedExpiryTokenSource(); } \ No newline at end of file diff --git a/src/Application/Features/Tenants/Commands/Rename/RenameTenant.cs b/src/Application/Features/Tenants/Commands/Rename/RenameTenant.cs index 37334aa2..776aa082 100644 --- a/src/Application/Features/Tenants/Commands/Rename/RenameTenant.cs +++ b/src/Application/Features/Tenants/Commands/Rename/RenameTenant.cs @@ -12,7 +12,7 @@ public class Command : ICacheInvalidatorRequest> { public required string Id { get; set; } public required string Name { get; set; } - public string CacheKey => TenantCacheKey.GetAllCacheKey; + public string[] CacheKeys => [TenantCacheKey.GetAllCacheKey]; public CancellationTokenSource? SharedExpiryTokenSource => TenantCacheKey.SharedExpiryTokenSource(); diff --git a/src/Application/Pipeline/CacheInvalidationBehaviour.cs b/src/Application/Pipeline/CacheInvalidationBehaviour.cs index a74276de..f4a697e3 100644 --- a/src/Application/Pipeline/CacheInvalidationBehaviour.cs +++ b/src/Application/Pipeline/CacheInvalidationBehaviour.cs @@ -26,9 +26,10 @@ CancellationToken cancellationToken { logger.LogTrace("{Name} cache expire with {@Request}", nameof(request), request); var response = await next().ConfigureAwait(false); - if (!string.IsNullOrEmpty(request.CacheKey)) + + foreach (var key in request.CacheKeys) { - cache.Remove(request.CacheKey); + cache.Remove(key); } request.SharedExpiryTokenSource?.Cancel(); diff --git a/src/Domain/Common/Enums/EnrolmentStatus.cs b/src/Domain/Common/Enums/EnrolmentStatus.cs index c5cfafc3..4e51ca5a 100644 --- a/src/Domain/Common/Enums/EnrolmentStatus.cs +++ b/src/Domain/Common/Enums/EnrolmentStatus.cs @@ -43,12 +43,17 @@ public SubmittedToProvider() protected override EnrolmentStatus[] GetAllowedTransitions() => [AbandonedStatus, PendingStatus]; + + public override bool StatusSupportsReassessment() => false; + } private sealed class SubmittedToAuthority : EnrolmentStatus { public SubmittedToAuthority(): base(nameof(SubmittedToAuthority), 2) { } + public override bool StatusSupportsReassessment() => false; + protected override EnrolmentStatus[] GetAllowedTransitions() => [ SubmittedToProviderStatus, ApprovedStatus ]; } @@ -72,4 +77,8 @@ protected override EnrolmentStatus[] GetAllowedTransitions() => } + /// + /// Indicates that a participant at this enrolment stage is allowed to have a new assessment created + /// + public virtual bool StatusSupportsReassessment() => true; } diff --git a/src/Infrastructure/Constants/ConstantString.cs b/src/Infrastructure/Constants/ConstantString.cs index 4ff7ca58..3cc17071 100644 --- a/src/Infrastructure/Constants/ConstantString.cs +++ b/src/Infrastructure/Constants/ConstantString.cs @@ -110,7 +110,8 @@ public static string Localize(string key) public static string ArchiveConfirmationTitle => Localize("Archive Confirmation"); public static string LogoutConfirmationTitle => Localize("Logout Confirmation"); - public static string ResumeEnrolment => Localize("Continue Enrolment"); + public static string NewEnrolment => Localize("New Enrolment"); + public static string ResumeEnrolment => Localize("Resume Enrolment"); public static string LogoutConfirmation => Localize("You are attempting to log out of application. Do you really want to log out?"); diff --git a/src/Server.UI/Pages/Assessment/Assessment.razor b/src/Server.UI/Pages/Assessment/Assessment.razor index 2774e6e2..7ad260f3 100644 --- a/src/Server.UI/Pages/Assessment/Assessment.razor +++ b/src/Server.UI/Pages/Assessment/Assessment.razor @@ -9,7 +9,7 @@ { + ActiveStepChanged="@OnStepChange" ShowSkipButton="false" > @*This top-level form is responsible for submission *@ diff --git a/src/Server.UI/Pages/Participants/Components/CaseSummary.razor b/src/Server.UI/Pages/Participants/Components/CaseSummary.razor index 041c2963..450e3cde 100644 --- a/src/Server.UI/Pages/Participants/Components/CaseSummary.razor +++ b/src/Server.UI/Pages/Participants/Components/CaseSummary.razor @@ -1,43 +1,76 @@ +@using Cfo.Cats.Application.Common.Interfaces.Identity @using Cfo.Cats.Application.Features.Assessments.Commands @using Cfo.Cats.Application.Features.Participants.DTOs - + + + + A + + Latest Assessment - - - - No assessment found - Begin + + @if (_latestAssessment is null) + { + No assessment has been created. + } + else + { +
+
+
+
+ Date Created +
+
+ @_latestAssessment.AssessmentDate!.Value.ToString("dd/MM/yyyy") +
+
+
+
+ Created By +
+
+ @UserService.DataSource.First(u => u.Id == _latestAssessment.AssessmentCreator).DisplayName +
+
+ +
+ + +
+ + }
+ + + @if (CanBeginAssessment()) + { + + + + + } + @if (CanContinueAssessment()) + { + + + + } + @if (CanReassess()) + { + + + + } +
-
- -@code { - - - [CascadingParameter] - public ParticipantDto ParticipantDto { get; set; } = default!; - - public async Task BeginAssessment() - { - var command = new BeginAssessment.Command() - { - ParticipantId = this.ParticipantDto.Id - }; - var result = await Mediator.Send(command); - - if (result.Succeeded) - { - Navigation.NavigateTo($"/pages/participants/{ParticipantDto.Id}/assessment/{result.Data}"); - } - } - -} \ No newline at end of file + \ No newline at end of file diff --git a/src/Server.UI/Pages/Participants/Components/CaseSummary.razor.cs b/src/Server.UI/Pages/Participants/Components/CaseSummary.razor.cs new file mode 100644 index 00000000..619f6dcf --- /dev/null +++ b/src/Server.UI/Pages/Participants/Components/CaseSummary.razor.cs @@ -0,0 +1,73 @@ +using Cfo.Cats.Application.Common.Interfaces.Identity; +using Cfo.Cats.Application.Features.Assessments.Commands; +using Cfo.Cats.Application.Features.Participants.DTOs; +using Cfo.Cats.Domain.Common.Enums; + +namespace Cfo.Cats.Server.UI.Pages.Participants.Components; + +public partial class CaseSummary +{ + private AssessmentSummaryDto? _latestAssessment; + [Inject] private IUserService UserService { get; set; } = default!; + [CascadingParameter] + public ParticipantSummaryDto ParticipantSummaryDto { get; set; } = default!; + protected override void OnParametersSet() + { + base.OnParametersSet(); + _latestAssessment = ParticipantSummaryDto.Assessments is [] + ? null + : ParticipantSummaryDto.Assessments.OrderByDescending(a => a.AssessmentDate) + .First(); + } + public async Task BeginAssessment() + { + var command = new BeginAssessment.Command + { + ParticipantId = ParticipantSummaryDto.Id + }; + var result = await Mediator.Send(command); + + if (result.Succeeded) + { + Navigation.NavigateTo($"/pages/participants/{ParticipantSummaryDto.Id}/assessment/{result.Data}"); + } + } + + public void ContinueAssessment() + { + Navigation.NavigateTo($"/pages/participants/{ParticipantSummaryDto.Id}/assessment/{_latestAssessment!.AssessmentId}"); + } + + /// + /// If true, indicates we are creating our first ever assessment. + /// + private bool CanBeginAssessment() => _latestAssessment == null; + + /// + /// If true indicates we have an assessment that is continuable + /// (i.e. not scored) + /// + /// + /// + private bool CanContinueAssessment() + { + return _latestAssessment is + { + AssessmentScored: false + } ; + } + + /// + /// If true indicates we have an assessment that is recreatable + /// (i.e. we have a scored assessment and are not in QA) + /// + private bool CanReassess() + { + return _latestAssessment is + { + AssessmentScored: true + } && + ParticipantSummaryDto.EnrolmentStatus.StatusSupportsReassessment(); + + } +} diff --git a/src/Server.UI/Pages/Participants/Participant.razor b/src/Server.UI/Pages/Participants/Participant.razor index 8c2d5357..604ef2a4 100644 --- a/src/Server.UI/Pages/Participants/Participant.razor +++ b/src/Server.UI/Pages/Participants/Participant.razor @@ -15,7 +15,7 @@ @if (_participant is not null) { - + @@ -28,7 +28,7 @@ - @_participant.CurrentLocation.Name + @_participant.Location @@ -41,7 +41,7 @@ - @_participant.DateOfBirth.GetValueOrDefault().ToShortDateString() (@_participant.DateOfBirth.Humanize()) + @_participant.DateOfBirth @@ -54,7 +54,7 @@ - @_participant.EnrolmentStatus!.Name + @_participant.EnrolmentStatus!.Name.Humanize() @@ -63,7 +63,7 @@ - + @@ -89,14 +89,18 @@ @code { [Parameter] public string Id { get; set; } = default!; + + [CascadingParameter] + public UserProfile UserProfile { get; set; } = default!; - private ParticipantDto? _participant = null; + private ParticipantSummaryDto? _participant = null; protected override async Task OnInitializedAsync() { - _participant = await Mediator.Send(new GetParticipantById.Query() + _participant = await Mediator.Send(new GetParticipantSummary.Query() { - Id = Id + ParticipantId = Id, + CurrentUser = UserProfile }); } } \ No newline at end of file diff --git a/src/Server.UI/Pages/Participants/Participants.razor b/src/Server.UI/Pages/Participants/Participants.razor index fff45460..61be9377 100644 --- a/src/Server.UI/Pages/Participants/Participants.razor +++ b/src/Server.UI/Pages/Participants/Participants.razor @@ -1,8 +1,8 @@ @page "/pages/participants" -@using Cfo.Cats.Application.Features.Documents.Commands.Transistion @using Cfo.Cats.Domain.Common.Enums @using Cfo.Cats.Server.UI.Pages.Participants.Components @using Cfo.Cats.Application.Features.Participants.Caching +@using Cfo.Cats.Application.Features.Participants.Commands.Transistion @using Cfo.Cats.Application.Features.Participants.DTOs @using Cfo.Cats.Application.Features.Participants.Queries @using Cfo.Cats.Application.Features.Participants.Specifications @@ -35,24 +35,34 @@
- @Title + @L[_currentDto.GetClassDescription()]
-
- +
- + @ConstantString.Refresh + @if (_canEnrol) + { + + @ConstantString.NewEnrolment + + } @@ -60,7 +70,7 @@
@if (_canSearch) { - } @@ -168,6 +178,8 @@ private MudDataGrid _table = default!; private bool _loading; private bool _canSearch; + private bool _canEnrol; + private ParticipantDto _currentDto = new() { Id = "" }; private ParticipantsWithPagination.Query Query { get; set; } = new(); @@ -179,7 +191,7 @@ private async Task OnSubmitToProviderQa(ParticipantDto participant) { - SubmitToProviderQa.Command command = new SubmitToProviderQa.Command() + SubmitToProviderQa.Command command = new SubmitToProviderQa.Command { ParticipantId = participant.Id }; @@ -208,6 +220,7 @@ Title = L["Participants"]; var state = await AuthState; _canSearch = (await AuthService.AuthorizeAsync(state.User, PolicyNames.AllowCandidateSearch)).Succeeded; + _canEnrol = (await AuthService.AuthorizeAsync(state.User, PolicyNames.AllowEnrol)).Succeeded; } private async Task> ServerReload(GridState state) @@ -231,6 +244,10 @@ private async Task OnSearch(string text) { + if (_loading) + { + return; + } _selectedItems = new(); Query.Keyword = text; await _table.ReloadServerData(); @@ -250,4 +267,8 @@ await _table.ReloadServerData(); } + private void OnEnrol() + { + Navigation.NavigateTo("/pages/candidates/search"); + } } \ No newline at end of file diff --git a/src/Server.UI/Services/Navigation/MenuService.cs b/src/Server.UI/Services/Navigation/MenuService.cs index fba5bf3c..1d0f51bd 100644 --- a/src/Server.UI/Services/Navigation/MenuService.cs +++ b/src/Server.UI/Services/Navigation/MenuService.cs @@ -27,7 +27,6 @@ public class MenuService : IMenuService IsParent = true, MenuItems = new List { - new() { Title = "New Enrolment", Href = "/pages/candidates/search" }, new() { Title = "All", diff --git a/src/Server.UI/wwwroot/css/app.css b/src/Server.UI/wwwroot/css/app.css index 9254744f..0f5984b3 100644 --- a/src/Server.UI/wwwroot/css/app.css +++ b/src/Server.UI/wwwroot/css/app.css @@ -78,4 +78,27 @@ :root{ --mud-palette-action-default: white; +} + +.description-list { + display: flex; + flex-direction: column; +} + +.description-pair { + display: flex; + align-items: center; + margin-bottom: 8px; +} + +.description-pair dt { + min-width: 100px; + margin-right: 8px; + font-weight: bold; + text-align: left; +} + +.description-pair dd { + margin: 0; + flex: 1; } \ No newline at end of file