@* This print header HAS to be within the main component and not in the header component otherwise there is a problem when From a67dc1357f32d2d2a1ef7ffbd5b977de4cb85e64 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Thu, 31 Jul 2025 15:56:11 +0100 Subject: [PATCH 011/124] padding changes --- LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml | 4 ++-- LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml index 37c10c367..f36ff9ee6 100644 --- a/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml @@ -11,11 +11,11 @@
-
+ -
+

My account

diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml index abb098425..6bbc6c8d6 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml @@ -25,11 +25,11 @@
-
+ -
+

My learning activity

From 5eedd65bc2c8899c9778fcff5cd9d71b0aa580a5 Mon Sep 17 00:00:00 2001 From: Binon Date: Thu, 31 Jul 2025 17:13:17 +0100 Subject: [PATCH 012/124] Moved entrolled courses function to openAPI --- .../Interfaces/IMoodleApiService.cs | 9 -- .../Interfaces/IMoodleHttpClient.cs | 23 ---- .../Services/DashboardService.cs | 14 +-- .../Services/MoodleApiService.cs | 93 +++----------- .../Services/MoodleHttpClient.cs | 87 ------------- .../Startup/ServiceMappings.cs | 10 -- .../Shared/Tenant/LearningHub/_Layout.cshtml | 22 ++-- .../Configuration/MoodleConfig.cs | 16 ++- ...gHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../Services/IMoodleApiService.cs | 21 +++- .../HttpClients/MoodleHttpClient.cs | 18 +-- .../Services/MoodleApiService.cs | 116 ++++++++++++++++-- .../Controllers/MoodleController.cs | 44 ++++++- .../LearningHub.Nhs.OpenApi/appsettings.json | 6 +- 14 files changed, 225 insertions(+), 256 deletions(-) delete mode 100644 LearningHub.Nhs.WebUI/Interfaces/IMoodleHttpClient.cs delete mode 100644 LearningHub.Nhs.WebUI/Services/MoodleHttpClient.cs diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs index 60dc18cb6..03ff433af 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs @@ -24,14 +24,5 @@ public interface IMoodleApiService /// pageNumber. /// List of MoodleCourseResponseModel. Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber); - - /// - /// GetEnrolledCoursesAsync. - /// - /// Moodle user id. - /// Moodle course id. - /// pageNumber. - /// List of MoodleCourseResponseModel. - Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber); } } diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMoodleHttpClient.cs b/LearningHub.Nhs.WebUI/Interfaces/IMoodleHttpClient.cs deleted file mode 100644 index 3348a20a9..000000000 --- a/LearningHub.Nhs.WebUI/Interfaces/IMoodleHttpClient.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace LearningHub.Nhs.Services.Interface -{ - using System.Net.Http; - using System.Threading.Tasks; - - /// - /// The Moodle Http Client interface. - /// - public interface IMoodleHttpClient - { - /// - /// The get cient async. - /// - /// The . - Task GetClient(); - - /// - /// GetDefaultParameters. - /// - /// defaultParameters. - string GetDefaultParameters(); - } -} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index d04708393..f0dfce65f 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -2,15 +2,11 @@ { using System; using System.Collections.Generic; - using System.Net.Http; - using System.Runtime.InteropServices.WindowsRuntime; using System.Text; using System.Threading.Tasks; using LearningHub.Nhs.Models.Dashboard; using LearningHub.Nhs.Models.Entities.Analytics; - using LearningHub.Nhs.Models.Entities.Reporting; using LearningHub.Nhs.Models.Moodle.API; - using LearningHub.Nhs.Services.Interface; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; using Microsoft.Extensions.Logging; @@ -21,19 +17,15 @@ /// public class DashboardService : BaseService, IDashboardService { - private readonly IMoodleHttpClient moodleHttpClient; - /// /// Initializes a new instance of the class. /// /// learningHubHttpClient. /// The Open Api Http Client. /// logger. - /// MoodleHttpClient. - public DashboardService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IMoodleHttpClient moodleHttpClient) + public DashboardService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) : base(learningHubHttpClient, openApiHttpClient, logger) { - this.moodleHttpClient = moodleHttpClient; } /// @@ -132,7 +124,7 @@ public async Task GetResourcesAsync(string d public async Task> GetEnrolledCoursesFromMoodleAsync(int currentUserId, int pageNumber) { List viewmodel = new List { }; - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient, this.OpenApiHttpClient); + MoodleApiService moodleApiService = new MoodleApiService(this.OpenApiHttpClient); viewmodel = await moodleApiService.GetEnrolledCoursesAsync(currentUserId, pageNumber); return viewmodel; } @@ -144,7 +136,7 @@ public async Task> GetEnrolledCoursesFromMoodleA /// A representing the result of the asynchronous operation. public async Task GetMoodleUserIdAsync(int currentUserId) { - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient, this.OpenApiHttpClient); + MoodleApiService moodleApiService = new MoodleApiService(this.OpenApiHttpClient); var moodleUserId = await moodleApiService.GetMoodleUserIdByUsernameAsync(currentUserId); return moodleUserId; } diff --git a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs index 2c4d3accb..4a2fb5cdf 100644 --- a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs @@ -2,33 +2,24 @@ { using System; using System.Collections.Generic; - using System.Net.Http; - using System.Text; - using System.Text.Json; using System.Threading.Tasks; using LearningHub.Nhs.Models.Moodle.API; - using LearningHub.Nhs.Services.Interface; using LearningHub.Nhs.WebUI.Interfaces; - using LearningHub.Nhs.WebUI.Models; using Newtonsoft.Json; - using MoodleCourseCompletionModel = Nhs.Models.Moodle.API.MoodleCourseCompletionModel; /// /// MoodleApiService. /// public class MoodleApiService : IMoodleApiService { - private readonly IMoodleHttpClient moodleHttpClient; private readonly IOpenApiHttpClient openApiHttpClient; /// /// Initializes a new instance of the class. /// - /// moodleHttpClient. /// The Open Api Http Client. - public MoodleApiService(IMoodleHttpClient moodleHttpClient, IOpenApiHttpClient openApiHttpClient) + public MoodleApiService(IOpenApiHttpClient openApiHttpClient) { - this.moodleHttpClient = moodleHttpClient; this.openApiHttpClient = openApiHttpClient; } @@ -70,89 +61,37 @@ public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) /// /// GetEnrolledCoursesAsync. /// - /// Moodle user id. + /// Moodle user id. /// The page Number. /// A representing the result of the asynchronous operation. - public async Task> GetEnrolledCoursesAsync(int userId, int pageNumber) + public async Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber) { - List viewmodel = new List { }; - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient, this.openApiHttpClient); + List viewmodel = new List(); - var client = await this.moodleHttpClient.GetClient(); - string additionalParameters = $"userid={userId}"; - string defaultParameters = this.moodleHttpClient.GetDefaultParameters(); - string url = $"&wsfunction=core_enrol_get_users_courses&{additionalParameters}"; - - HttpResponseMessage response = await client.GetAsync("?" + defaultParameters + url); - - if (response.IsSuccessStatusCode) + try { - var result = response.Content.ReadAsStringAsync().Result; + var client = await this.openApiHttpClient.GetClientAsync(); - using var document = JsonDocument.Parse(result); - var root = document.RootElement; + var request = $"Moodle/GetEnrolledCourses/{currentUserId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); - // Check if it's a JSON object and contains "exception" - if (!(root.ValueKind == JsonValueKind.Object && root.TryGetProperty("exception", out _))) + if (response.IsSuccessStatusCode) { + var result = response.Content.ReadAsStringAsync().Result; viewmodel = JsonConvert.DeserializeObject>(result); - - foreach (var course in viewmodel) - { - course.CourseCompletionViewModel = await moodleApiService.GetCourseCompletionAsync(userId, course.Id.Value, pageNumber); - } } - else + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) { - // Contains error, handle it as needed. + throw new Exception("AccessDenied"); } - } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || - response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - throw new Exception("AccessDenied"); - } - - return viewmodel; - } - /// - /// GetEnrolledCoursesAsync. - /// - /// Moodle user id. - /// Moodle course id. - /// pageNumber. - /// List of MoodleCourseResponseModel. - public async Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber) - { - MoodleCourseCompletionModel viewmodel = new MoodleCourseCompletionModel { }; - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient, this.openApiHttpClient); - - var client = await this.moodleHttpClient.GetClient(); - string additionalParameters = $"userid={userId}&courseid={courseId}"; - string defaultParameters = this.moodleHttpClient.GetDefaultParameters(); - string url = $"&wsfunction=core_completion_get_course_completion_status&{additionalParameters}"; - - HttpResponseMessage response = await client.GetAsync("?" + defaultParameters + url); - - if (response.IsSuccessStatusCode) - { - var result = response.Content.ReadAsStringAsync().Result; - - var canViewReport = JsonConvert.DeserializeObject(result); - - if (string.IsNullOrEmpty(canViewReport.Exception)) - { - viewmodel = JsonConvert.DeserializeObject(result); - } + return viewmodel; } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || - response.StatusCode == System.Net.HttpStatusCode.Forbidden) + catch (Exception ex) { - throw new Exception("AccessDenied"); + // this.Logger.LogError(string.Format("Error occurred in GetSearchResultAsync: {0}", ex.Message)); + return viewmodel; } - - return viewmodel; } } } diff --git a/LearningHub.Nhs.WebUI/Services/MoodleHttpClient.cs b/LearningHub.Nhs.WebUI/Services/MoodleHttpClient.cs deleted file mode 100644 index 79f44027a..000000000 --- a/LearningHub.Nhs.WebUI/Services/MoodleHttpClient.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace LearningHub.Nhs.Services -{ - using System; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Threading.Tasks; - using LearningHub.Nhs.Services.Interface; - using Microsoft.Extensions.Configuration; - - /// - /// The Moodle Http Client. - /// - public class MoodleHttpClient : IMoodleHttpClient, IDisposable - { - private readonly HttpClient httpClient = new (); - private bool initialised = false; - private string moodleAPIBaseUrl; - private string moodleAPIMoodleWSRestFormat; - private string moodleAPIWSToken; - - /// - /// Initializes a new instance of the class. - /// - /// httpClient. - /// config. - public MoodleHttpClient(HttpClient httpClient, IConfiguration config) - { - this.httpClient = httpClient; - this.moodleAPIBaseUrl = config["MoodleAPIConfig:BaseUrl"] + "webservice/rest/server.php"; - this.moodleAPIMoodleWSRestFormat = config["MoodleAPIConfig:MoodleWSRestFormat"]; - this.moodleAPIWSToken = config["MoodleAPIConfig:WSToken"]; - } - - /// - /// The Get Client method. - /// - /// The . - public async Task GetClient() - { - this.Initialise(this.moodleAPIBaseUrl); - return this.httpClient; - } - - /// - /// GetDefaultParameters. - /// - /// defaultParameters. - public string GetDefaultParameters() - { - string defaultParameters = $"wstoken={this.moodleAPIWSToken}" - + $"&moodlewsrestformat={this.moodleAPIMoodleWSRestFormat}"; - - return defaultParameters; - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// The dispoase. - /// - /// disposing. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this.httpClient.Dispose(); - } - } - - private void Initialise(string httpClientUrl) - { - if (this.initialised == false) - { - this.httpClient.BaseAddress = new Uri(httpClientUrl); - this.httpClient.DefaultRequestHeaders.Accept.Clear(); - this.httpClient.DefaultRequestHeaders.Accept.Add( - new MediaTypeWithQualityHeaderValue("application/json")); - this.initialised = true; - } - } - } -} diff --git a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs index 0a0c04c49..eca246956 100644 --- a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs +++ b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs @@ -3,8 +3,6 @@ using System.Net.Http; using GDS.MultiPageFormData; using LearningHub.Nhs.Models.OpenAthens; - using LearningHub.Nhs.Services; - using LearningHub.Nhs.Services.Interface; using LearningHub.Nhs.WebUI.Filters; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; @@ -62,13 +60,6 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }); - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler( - () => new HttpClientHandler - { - ServerCertificateCustomValidationCallback = - HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, - }); } else { @@ -76,7 +67,6 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); - services.AddHttpClient(); } // Config diff --git a/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml index 1214ec0f7..6b679b67e 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml @@ -170,17 +170,15 @@ @functions { public async Task DisplayOffline() { - return false; - - // if (ViewContext.RouteData.Values["controller"].ToString() != "Offline" && User.Identity.IsAuthenticated) - // { - // var internalSystem = await this.internalSystemService.GetByIdAsync((int)InternalSystemType.LearningHub); - - // return internalSystem.IsOffline; - // } - // else - // { - // return false; - // } + if (ViewContext.RouteData.Values["controller"].ToString() != "Offline" && User.Identity.IsAuthenticated) + { + var internalSystem = await this.internalSystemService.GetByIdAsync((int)InternalSystemType.LearningHub); + + return internalSystem.IsOffline; + } + else + { + return false; + } } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs index de178b6a2..20e9b1904 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs @@ -14,16 +14,26 @@ public class MoodleConfig /// /// Gets or sets the base url for the Moodle service. /// - public string APIBaseUrl { get; set; } = null!; + public string ApiBaseUrl { get; set; } = null!; /// /// Gets or sets the Web service Rest Format. /// - public string APIWSRestFormat { get; set; } = null!; + public string ApiWsRestFormat { get; set; } = null!; /// /// Gets or sets the token. /// - public string APIWSToken { get; set; } = null!; + public string ApiWsToken { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + public string ApiPath { get; set; } = "webservice/rest/server.php"; + + /// + /// Gets or sets the token. + /// + public string CoursePath { get; set; } = "course/view.php"; } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index aa019e18c..6fe3b2dfe 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -17,7 +17,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs index e366a4370..ee573d43b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs @@ -1,7 +1,5 @@ -using System; +using LearningHub.Nhs.Models.Moodle.API; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace LearningHub.Nhs.OpenApi.Services.Interface.Services @@ -17,5 +15,22 @@ public interface IMoodleApiService /// The current LH User Id. /// A representing the result of the asynchronous operation. Task GetMoodleUserIdByUsernameAsync(int currentUserId); + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// pageNumber. + /// List of MoodleCourseResponseModel. + Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber); + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// Moodle course id. + /// pageNumber. + /// List of MoodleCourseResponseModel. + Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs index 73af93977..2c9fc410a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs @@ -20,9 +20,9 @@ public class MoodleHttpClient : IMoodleHttpClient, IDisposable private readonly HttpClient httpClient = new(); private bool initialised = false; - private string moodleAPIBaseUrl; - private string moodleAPIMoodleWSRestFormat; - private string moodleAPIWSToken; + private string moodleApiUrl; + private string moodleApiMoodleWsRestFormat; + private string moodleApiWsToken; /// /// Initializes a new instance of the class. @@ -34,9 +34,9 @@ public MoodleHttpClient(HttpClient httpClient, IOptions moodleConf this.moodleConfig = moodleConfig.Value; this.httpClient = httpClient; - this.moodleAPIBaseUrl = this.moodleConfig.APIBaseUrl + "webservice/rest/server.php"; - this.moodleAPIMoodleWSRestFormat = this.moodleConfig.APIWSRestFormat; - this.moodleAPIWSToken = this.moodleConfig.APIWSToken; + this.moodleApiUrl = this.moodleConfig.ApiBaseUrl + this.moodleConfig.ApiPath; + this.moodleApiMoodleWsRestFormat = this.moodleConfig.ApiWsRestFormat; + this.moodleApiWsToken = this.moodleConfig.ApiWsToken; } /// @@ -45,7 +45,7 @@ public MoodleHttpClient(HttpClient httpClient, IOptions moodleConf /// The . public async Task GetClient() { - this.Initialise(this.moodleAPIBaseUrl); + this.Initialise(this.moodleApiUrl); return this.httpClient; } @@ -55,8 +55,8 @@ public async Task GetClient() /// defaultParameters. public string GetDefaultParameters() { - string defaultParameters = $"wstoken={this.moodleAPIWSToken}" - + $"&moodlewsrestformat={this.moodleAPIMoodleWSRestFormat}"; + string defaultParameters = $"wstoken={this.moodleApiWsToken}" + + $"&moodlewsrestformat={this.moodleApiMoodleWsRestFormat}"; return defaultParameters; } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs index 90ffc4280..08786db8d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs @@ -11,6 +11,7 @@ using System.Net; using System.Net.Http; using System.Text; + using System.Text.Json; using System.Threading.Tasks; /// @@ -31,6 +32,7 @@ public MoodleApiService(IMoodleHttpClient moodleHttpClient, ILogger /// GetMoodleUserIdByUsernameAsync. /// @@ -51,16 +53,89 @@ public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) } + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// The page Number. + /// A representing the result of the asynchronous operation. + public async Task> GetEnrolledCoursesAsync(int userId, int pageNumber) + { + var parameters = new Dictionary + { + { "userid", userId.ToString() } + }; + + // Fetch enrolled courses + var enrolledCourses = await GetCallMoodleApiAsync>( + "core_enrol_get_users_courses", + parameters + ); + + if (enrolledCourses == null || enrolledCourses.Count == 0) + return new List(); + + // Load course completion info in parallel + var completionTasks = enrolledCourses + .Where(c => c.Id.HasValue) + .Select(async course => + { + try + { + course.CourseCompletionViewModel = await GetCourseCompletionAsync(userId, course.Id.Value, pageNumber); + } + catch (Exception ex) + { + course.CourseCompletionViewModel = new MoodleCourseCompletionModel + { + CompletionStatus = null, + }; + } + + return course; + }); + + var enrichedCourses = await Task.WhenAll(completionTasks); + + return enrichedCourses.ToList(); + + } + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// Moodle course id. + /// pageNumber. + /// List of MoodleCourseResponseModel. + public async Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber) + { + var parameters = new Dictionary + { + { "userid", userId.ToString() }, + { "courseid", courseId.ToString() } + }; + + // Call Moodle API and parse response + var result = await GetCallMoodleApiAsync("core_completion_get_course_completion_status", parameters); + + // If Moodle did not return an exception, return parsed completion data + if (result.Warnings.Count == 0) + { + // Optionally map/convert if needed + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result)); + } + + return new MoodleCourseCompletionModel(); // Return empty model or null as fallback + } + private async Task GetCallMoodleApiAsync(string wsFunction, Dictionary parameters) { var client = await this.moodleHttpClient.GetClient(); string defaultParameters = this.moodleHttpClient.GetDefaultParameters(); - // Build URL query - var queryBuilder = new StringBuilder(); - - queryBuilder.Append($"&wsfunction={wsFunction}"); - + // Build URL query string + var queryBuilder = new StringBuilder($"&wsfunction={wsFunction}"); foreach (var param in parameters) { queryBuilder.Append($"&{param.Key}={Uri.EscapeDataString(param.Value)}"); @@ -73,7 +148,33 @@ private async Task GetCallMoodleApiAsync(string wsFunction, Dictionary(result); + // Moodle may still return an error with 200 OK + try + { + using var document = JsonDocument.Parse(result); + var root = document.RootElement; + + if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty("exception", out var exceptionProp)) + { + string? message = root.TryGetProperty("message", out var messageProp) + ? messageProp.GetString() + : "Unknown error"; + + this.logger.LogError($"Moodle returned an exception: {exceptionProp.GetString()}, Message: {message}"); + throw new Exception($"Moodle API Error: {exceptionProp.GetString()}, Message: {message}"); + } + } + catch (System.Text.Json.JsonException ex) + { + this.logger.LogError(ex, "Failed to parse Moodle API response as JSON."); + throw; + } + + var deserialized = JsonConvert.DeserializeObject(result); + + return deserialized == null + ? throw new Exception($"Failed to deserialize Moodle API response into type {typeof(T).Name}. Raw response: {result}") + : deserialized; } else if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden) { @@ -82,9 +183,10 @@ private async Task GetCallMoodleApiAsync(string wsFunction, Dictionary @@ -14,14 +16,33 @@ public class MoodleController : Controller { private readonly IMoodleApiService moodleService; + private readonly MoodleConfig moodleConfig; /// /// Initializes a new instance of the class. /// /// The moodle service. - public MoodleController(IMoodleApiService moodleService) + /// Moodel config. + public MoodleController(IMoodleApiService moodleService, IOptions moodleConfig) { this.moodleService = moodleService; + this.moodleConfig = moodleConfig.Value; + } + + /// + /// The GetSafeMoodleConfig. + /// + /// The . + [HttpGet] + [Route("GetSafeMoodleConfig")] + public IActionResult GetSafeMoodleConfig() + { + // Only expose safe config values + return Ok(new + { + apiBaseUrl = this.moodleConfig.ApiBaseUrl, + coursePath = this.moodleConfig.CoursePath, + }); } /// @@ -43,5 +64,26 @@ public async Task GetMoodleUserId(int? currentUserId) return this.Ok(0); } } + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// The page Number. + /// A representing the result of the asynchronous operation. + [HttpGet] + [Route("GetEnrolledCourses/{currentUserId?}")] + public async Task GetEnrolledCoursesAsync(int? currentUserId, int pageNumber = 0) + { + if (currentUserId.HasValue) + { + var entrolledCourses = await this.moodleService.GetEnrolledCoursesAsync(currentUserId.Value, pageNumber); + return this.Ok(entrolledCourses); + } + else + { + return this.Ok(0); + } + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 61417224f..181c8eaf8 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -121,8 +121,8 @@ "AuthTimeout": 20 }, "Moodle": { - "APIBaseUrl": "", - "APIWSRestFormat": "json", - "APIWSToken": "" + "ApiBaseUrl": "", + "ApiWsRestFormat": "json", + "ApiWsToken": "" } } From 58da86806d2adcc86569858aa3c4378fd896c7bd Mon Sep 17 00:00:00 2001 From: Binon Date: Fri, 1 Aug 2025 12:10:23 +0100 Subject: [PATCH 013/124] tidied up some code --- .../Configuration/MoodleApiConfig.cs | 33 +++++++++++++++++++ .../Interfaces/IMoodleApiService.cs | 7 ++++ .../ServiceCollectionExtension.cs | 3 ++ .../Services/DashboardService.cs | 12 ++++--- .../Services/MoodleApiService.cs | 19 ++++++++++- .../Startup/ServiceMappings.cs | 1 + .../Views/Home/_CourseEnrolled.cshtml | 8 ++--- .../Views/Search/_ResourceSearchResult.cshtml | 29 +++++++++++----- .../Controllers/MoodleController.cs | 21 +----------- 9 files changed, 92 insertions(+), 41 deletions(-) create mode 100644 LearningHub.Nhs.WebUI/Configuration/MoodleApiConfig.cs diff --git a/LearningHub.Nhs.WebUI/Configuration/MoodleApiConfig.cs b/LearningHub.Nhs.WebUI/Configuration/MoodleApiConfig.cs new file mode 100644 index 000000000..ec874477e --- /dev/null +++ b/LearningHub.Nhs.WebUI/Configuration/MoodleApiConfig.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.WebUI.Configuration +{ + /// + /// The Moodle Settings. + /// + public class MoodleApiConfig + { + /// + /// Gets or sets the base url for the Moodle service. + /// + public string BaseUrl { get; set; } = null!; + + /// + /// Gets or sets the Web service Rest Format. + /// + public string MoodleWSRestFormat { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + public string WSToken { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + public string ApiPath { get; set; } = "webservice/rest/server.php"; + + /// + /// Gets or sets the token. + /// + public string CoursePath { get; set; } = "course/view.php"; + } +} diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs index 03ff433af..5796c3482 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs @@ -24,5 +24,12 @@ public interface IMoodleApiService /// pageNumber. /// List of MoodleCourseResponseModel. Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber); + + /// + /// GetCourseUrl. + /// + /// course Id. + /// return course URL. + string GetCourseUrl(int courseId); } } diff --git a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs index 5ed5cc926..eb2c2fa2c 100644 --- a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs +++ b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs @@ -78,6 +78,9 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + var moodleApiSettings = configuration.GetSection("MoodleAPIConfig"); + services.Configure(moodleApiSettings); + var settingsSection = configuration.GetSection("Settings"); services.Configure(settingsSection); diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index f0dfce65f..bde127b81 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -17,15 +17,19 @@ /// public class DashboardService : BaseService, IDashboardService { + private readonly IMoodleApiService moodleApiService; + /// /// Initializes a new instance of the class. /// /// learningHubHttpClient. /// The Open Api Http Client. /// logger. - public DashboardService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + /// MoodleApiService. + public DashboardService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IMoodleApiService moodleApiService) : base(learningHubHttpClient, openApiHttpClient, logger) { + this.moodleApiService = moodleApiService; } /// @@ -124,8 +128,7 @@ public async Task GetResourcesAsync(string d public async Task> GetEnrolledCoursesFromMoodleAsync(int currentUserId, int pageNumber) { List viewmodel = new List { }; - MoodleApiService moodleApiService = new MoodleApiService(this.OpenApiHttpClient); - viewmodel = await moodleApiService.GetEnrolledCoursesAsync(currentUserId, pageNumber); + viewmodel = await this.moodleApiService.GetEnrolledCoursesAsync(currentUserId, pageNumber); return viewmodel; } @@ -136,8 +139,7 @@ public async Task> GetEnrolledCoursesFromMoodleA /// A representing the result of the asynchronous operation. public async Task GetMoodleUserIdAsync(int currentUserId) { - MoodleApiService moodleApiService = new MoodleApiService(this.OpenApiHttpClient); - var moodleUserId = await moodleApiService.GetMoodleUserIdByUsernameAsync(currentUserId); + var moodleUserId = await this.moodleApiService.GetMoodleUserIdByUsernameAsync(currentUserId); return moodleUserId; } diff --git a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs index 4a2fb5cdf..c80a764f0 100644 --- a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs @@ -4,7 +4,9 @@ using System.Collections.Generic; using System.Threading.Tasks; using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Interfaces; + using Microsoft.Extensions.Options; using Newtonsoft.Json; /// @@ -13,14 +15,17 @@ public class MoodleApiService : IMoodleApiService { private readonly IOpenApiHttpClient openApiHttpClient; + private readonly MoodleApiConfig configuration; /// /// Initializes a new instance of the class. /// /// The Open Api Http Client. - public MoodleApiService(IOpenApiHttpClient openApiHttpClient) + /// configuration. + public MoodleApiService(IOpenApiHttpClient openApiHttpClient, IOptions configuration) { this.openApiHttpClient = openApiHttpClient; + this.configuration = configuration.Value; } /// @@ -93,5 +98,17 @@ public async Task> GetEnrolledCoursesAsync(int c return viewmodel; } } + + /// + /// GetCourseUrl. + /// + /// course Id. + /// return course URL. + public string GetCourseUrl(int courseId) + { + var apiBaseUrl = this.configuration.BaseUrl; + string path = this.configuration.CoursePath; + return $"{apiBaseUrl}{path}?id={courseId}"; + } } } diff --git a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs index eca246956..9fd65bd60 100644 --- a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs +++ b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs @@ -98,6 +98,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); diff --git a/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml index f62eea9f2..a7351226b 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml @@ -3,7 +3,7 @@ @using LearningHub.Nhs.WebUI.Extensions @using LearningHub.Nhs.WebUI.Helpers @model MoodleCourseResponseModel -@inject Microsoft.Extensions.Configuration.IConfiguration Configuration; +@inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; @{ bool providerExists = false; @@ -11,11 +11,7 @@ string GetMoodleCourseUrl(int courseId) { - var apiBaseUrl = Configuration["MoodleAPIConfig:BaseUrl"]; - string path = $"course/view.php"; - string returnUrl = $@"{apiBaseUrl}{path}?id={courseId}"; - - return returnUrl; + return moodleApiService.GetCourseUrl(courseId); } }
diff --git a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml index 1db24b70e..50c8df8f4 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml @@ -1,5 +1,5 @@ @model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel -@inject Microsoft.Extensions.Configuration.IConfiguration Configuration; +@inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; @using System.Linq; @using System.Web; @@ -28,19 +28,30 @@ &query={searchSignalQueryEncoded}&title={payload?.DocumentFields?.Title}"; } - string GetMoodleCourseUrl(string courseId) + string GetMoodleCourseUrl(string courseIdWithPrefix) { - var prefix = "M"; - if (courseId.StartsWith(prefix)) + const string prefix = "M"; + + if (string.IsNullOrWhiteSpace(courseIdWithPrefix)) + { + return string.Empty; + } + + if (!courseIdWithPrefix.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { - courseId = courseId.Replace(prefix, ""); ; + return string.Empty; } - var apiBaseUrl = Configuration["MoodleAPIConfig:BaseUrl"]; - string path = $"course/view.php"; - string returnUrl = $@"{apiBaseUrl}{path}?id={courseId}"; + var courseIdPart = courseIdWithPrefix.Substring(prefix.Length); - return returnUrl; + if (int.TryParse(courseIdPart, out int courseId)) + { + return moodleApiService.GetCourseUrl(courseId); + } + else + { + return string.Empty; + } } bool showCatalogueFieldsInResources = ViewBag.ShowCatalogueFieldsInResources == null || ViewBag.ShowCatalogueFieldsInResources == true; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs index 646245053..f2639d005 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs @@ -16,33 +16,14 @@ public class MoodleController : Controller { private readonly IMoodleApiService moodleService; - private readonly MoodleConfig moodleConfig; /// /// Initializes a new instance of the class. /// /// The moodle service. - /// Moodel config. - public MoodleController(IMoodleApiService moodleService, IOptions moodleConfig) + public MoodleController(IMoodleApiService moodleService) { this.moodleService = moodleService; - this.moodleConfig = moodleConfig.Value; - } - - /// - /// The GetSafeMoodleConfig. - /// - /// The . - [HttpGet] - [Route("GetSafeMoodleConfig")] - public IActionResult GetSafeMoodleConfig() - { - // Only expose safe config values - return Ok(new - { - apiBaseUrl = this.moodleConfig.ApiBaseUrl, - coursePath = this.moodleConfig.CoursePath, - }); } /// From fcff0032a681acb1cba1f741956b361a0d403ece Mon Sep 17 00:00:00 2001 From: Arunima George Date: Fri, 1 Aug 2025 15:08:29 +0100 Subject: [PATCH 014/124] TD-5761: Draft commit --- .../LearningHub.Nhs.AdminUI.csproj | 2 +- ...rningHub.Nhs.WebUI.AutomatedUiTests.csproj | 1 + .../Controllers/MyAccountController.cs | 153 ++++++- .../Interfaces/IUserService.cs | 26 ++ .../LearningHub.Nhs.WebUI.csproj | 2 +- .../SideMenu/SideNavigationConfiguration.cs | 24 +- .../MyAccountEmploymentDetailsViewModel.cs | 95 +++++ .../MyAccountPersonalDetailsViewModel.cs | 64 +++ .../UserProfile/MyAccountSecurityViewModel.cs | 45 ++ LearningHub.Nhs.WebUI/Services/UserService.cs | 155 +++++++ .../Styles/layout/_layout.scss | 35 ++ .../Views/MyAccount/ChangeLocation.cshtml | 194 +++++++++ .../MyAccount/ChangePersonalDetails.cshtml | 110 +++++ .../Views/MyAccount/Index.cshtml | 393 ++++-------------- .../Views/MyAccount/MyAccountSecurity.cshtml | 126 ++++++ .../Views/MyAccount/MyEmployment.cshtml | 108 +++++ .../Views/MyAccount/_LeftNav.cshtml | 43 ++ .../Views/Notification/Index.cshtml | 4 +- ...gHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Tests.csproj | 1 + .../LearningHub.NHS.OpenAPI.csproj | 1 + .../LearningHub.Nhs.Api.csproj | 2 +- .../LearningHub.Nhs.Api.Shared.csproj | 2 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 2 +- ...earningHub.Nhs.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.Repository.csproj | 2 +- .../LearningHub.Nhs.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Services.csproj | 2 +- ...earningHub.Nhs.Migration.ConsoleApp.csproj | 2 +- ...LearningHub.Nhs.Migration.Interface.csproj | 2 +- .../LearningHub.Nhs.Migration.Models.csproj | 2 +- ...ub.Nhs.Migration.Staging.Repository.csproj | 2 +- ...LearningHub.Nhs.Migration.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Migration.csproj | 2 +- 36 files changed, 1269 insertions(+), 345 deletions(-) create mode 100644 LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountEmploymentDetailsViewModel.cs create mode 100644 LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountPersonalDetailsViewModel.cs create mode 100644 LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountSecurityViewModel.cs create mode 100644 LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLocation.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/MyAccount/ChangePersonalDetails.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountSecurity.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/MyAccount/MyEmployment.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/MyAccount/_LeftNav.cshtml diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index a0de54f8f..019a073ab 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -83,7 +83,7 @@ - + diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj index 1569f9734..ed0aaa413 100644 --- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -11,6 +11,7 @@ + diff --git a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs index d1dadb902..61c119f2a 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs @@ -131,8 +131,91 @@ public async Task Index(string returnUrl = null, bool? checkDetai } } + var userPersonalDetails = await this.userService.GetMyAccountPersonalDetailsAsync(); + ////var userProfileSummary = await this.userService.GetUserProfileSummaryAsync(); + ////var userProfileSummary1 = await this.userService.GetCurrentUserBasicDetailsAsync(); + return this.View("Index", userPersonalDetails); + } + + /// + /// MyEmploymentDetails. + /// + /// IActionResult. + [HttpGet] + [Route("myaccount-employement")] + public async Task MyEmploymentDetails() + { + var employmentDetails = await this.userService.GetMyEmploymentDetailsAsync(); + return this.View("MyEmployment", employmentDetails); + } + + /// + /// User profile actions. + /// + /// The redirect back url. + /// Whether to check account details. + /// IActionResult. + [HttpGet] + [Route("myaccount-security")] + public async Task MyAccountSecurity(string returnUrl = null, bool? checkDetails = false) + { + var securityDetails = await this.userService.GetMyAccountSecurityDetailsAsync(); + return this.View("MyAccountSecurity", securityDetails); + } + + /// + /// ChangePersonalDetails. + /// + /// ActionResult. + [HttpGet] + [Route("myaccount/ChangePersonalDetails")] + public async Task ChangePersonalDetails() + { + var userPersonalDetails = await this.userService.GetMyAccountPersonalDetailsAsync(); + return this.View("ChangePersonalDetails", userPersonalDetails); + } + + /// + /// To Update first name. + /// + /// model. + /// ActionResult. + [HttpPost] + public async Task UpdatePersonalDetails(MyAccountPersonalDetailsViewModel model) + { + if (!this.ModelState.IsValid) + { + return this.View("ChangePersonalDetails", model); + } + + await this.userService.UpdateMyAccountPersonalDetailsAsync(this.CurrentUserId, model); + await this.MyAccountUpdatePrimaryEmail(model.PrimaryEmailAddress); + this.ViewBag.SuccessMessage = "Success"; + return this.View("SuccessMessage"); + } + + /// + /// ChangeLocation. + /// + /// country id. + /// ActionResult. + [HttpGet] + [Route("myaccount/ChangeLocation")] + public async Task ChangeLocation(int? selectedCountryId) + { + this.TempData.Clear(); + var userLocationViewModel = await this.userService.GetUserLocationDetailsAsync(); var userProfileSummary = await this.userService.GetUserProfileSummaryAsync(); - return this.View("Index", userProfileSummary); + if (selectedCountryId.HasValue) + { + userLocationViewModel.SelectedCountryId = selectedCountryId; + } + + await this.multiPageFormService.SetMultiPageFormData( + userLocationViewModel, + MultiPageFormDataFeature.AddRegistrationPrompt, + this.TempData); + return this.View("ChangeLocation", new Tuple(userProfileSummary, userLocationViewModel)); } /// @@ -827,8 +910,10 @@ await this.userService.UpdateUserEmployment( LocationId = profile.LocationId, }); - this.ViewBag.SuccessMessage = "Your job details have been changed"; - return this.View("SuccessMessage"); + ////this.ViewBag.SuccessMessage = "Your job details have been changed"; + ////return this.View("SuccessMessage"); + + return this.RedirectToAction(nameof(this.ChangePrimarySpecialty), new UserPrimarySpecialtyUpdateViewModel { }); } else { @@ -887,8 +972,10 @@ await this.userService.UpdateUserEmployment( LocationId = profile.LocationId, }); - this.ViewBag.SuccessMessage = "Your primary specialty has been changed"; - return this.View("SuccessMessage"); + ////this.ViewBag.SuccessMessage = "Your primary specialty has been changed"; + ////return this.View("SuccessMessage"); + + return this.RedirectToAction(nameof(this.ChangeStartDate), new UserStartDateUpdateViewModel { }); } else { @@ -940,8 +1027,10 @@ await this.userService.UpdateUserEmployment( LocationId = profile.LocationId, }); - this.ViewBag.SuccessMessage = "Your job start date has been changed"; - return this.View("SuccessMessage"); + ////this.ViewBag.SuccessMessage = "Your job start date has been changed"; + ////return this.View("SuccessMessage"); + + return this.RedirectToAction(nameof(this.ChangeWorkPlace), new UserWorkPlaceUpdateViewModel { }); } } else @@ -1012,7 +1101,7 @@ await this.userService.UpdateUserEmployment( await this.cacheService.SetAsync(this.LoginWizardCacheKey, Newtonsoft.Json.JsonConvert.SerializeObject(loginWizardViewModel)); } - this.ViewBag.SuccessMessage = "Your place of work has been changed"; + this.ViewBag.SuccessMessage = "Your employment details have been updated"; return this.View("SuccessMessage"); } @@ -1197,5 +1286,53 @@ public async Task CancelEmailChangeValidationToken() this.ViewBag.SuccessMessage = CommonValidationErrorMessages.EmailCancelMessage; return this.View("SuccessMessage"); } + + private async Task MyAccountUpdatePrimaryEmail(string primaryEmailAddress) + { + bool userPrimaryEmailAddressChanged = false; + var user = await this.userService.GetUserByUserIdAsync(this.CurrentUserId); + if (user != null) + { + if (!string.IsNullOrEmpty(primaryEmailAddress) && user.EmailAddress.ToLower() != primaryEmailAddress.ToLower()) + { + userPrimaryEmailAddressChanged = true; + } + } + + if (userPrimaryEmailAddressChanged) + { + if (await this.userService.DoesEmailAlreadyExist(primaryEmailAddress)) + { + this.ModelState.AddModelError( + nameof(primaryEmailAddress), + CommonValidationErrorMessages.DuplicateEmailAddress); + ////return this.View("ChangePrimaryEmail", primaryEmailAddress); + } + else + { + var isUserRoleUpgrade = await this.userService.ValidateUserRoleUpgradeAsync(user.EmailAddress, primaryEmailAddress); + UserRoleUpgrade userRoleUpgradeModel = new UserRoleUpgrade() + { + UserId = this.CurrentUserId, + EmailAddress = primaryEmailAddress, + }; + if (isUserRoleUpgrade) + { + userRoleUpgradeModel.UserHistoryTypeId = (int)UserHistoryType.UserRoleUpgarde; + } + else + { + userRoleUpgradeModel.UserHistoryTypeId = (int)UserHistoryType.UserDetails; + } + + await this.userService.GenerateEmailChangeValidationTokenAndSendEmailAsync(primaryEmailAddress, isUserRoleUpgrade); + await this.userService.UpdateUserRoleUpgradeAsync(); + await this.userService.CreateUserRoleUpgradeAsync(userRoleUpgradeModel); + ////this.ViewBag.SuccessMessage = CommonValidationErrorMessages.EmailChangeRequestedSucessMessage; + ////this.ViewBag.Status = "Valid"; + ////return this.View("ConfirmEmailSuccessMessage"); + } + } + } } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs b/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs index 19ee0a927..320dcb9c3 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs @@ -478,5 +478,31 @@ public interface IUserService /// the string. /// base64 string. string Base64MD5HashDigest(string szString); + + /// + /// The get current user profile for My account. + /// + /// The . + Task GetMyAccountPersonalDetailsAsync(); + + /// + /// Update MyAccount Personal Details Async. + /// + /// userId. + /// MyAccountPersonalDetailsViewModel. + /// The . + Task UpdateMyAccountPersonalDetailsAsync(int userId, MyAccountPersonalDetailsViewModel model); + + /// + /// Get MyEmployment Details Async. + /// + /// The . + Task GetMyEmploymentDetailsAsync(); + + /// + /// Get MyAccount Security Details Async. + /// + /// The . + Task GetMyAccountSecurityDetailsAsync(); } } diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index dc2cf661d..9ff630819 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -107,7 +107,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs b/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs index 9f95fc173..4889d7fb8 100644 --- a/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs +++ b/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs @@ -26,30 +26,30 @@ public static IEnumerable GetGroupedMenus() new SideNavigationItem { Text = "Personal details", - Controller = "Account", - Action = "PersonalDetails", - IsActive = route => MatchRoute(route, "Account", "PersonalDetails"), + Controller = "MyAccount", + Action = "Index", + IsActive = route => MatchRoute(route, "MyAccount", "PersonalDetails"), }, new SideNavigationItem { Text = "My employment", - Controller = "Account", - Action = "MyEmployment", - IsActive = route => MatchRoute(route, "Account", "MyEmployment"), + Controller = "MyAccount", + Action = "MyEmploymentDetails", + IsActive = route => MatchRoute(route, "MyAccount", "MyEmploymentDetails"), }, new SideNavigationItem { Text = "Security", - Controller = "Account", - Action = "Security", - IsActive = route => MatchRoute(route, "Account", "Security"), + Controller = "MyAccount", + Action = "MyAccountSecurity", + IsActive = route => MatchRoute(route, "MyAccount", "MyAccountSecurity"), }, new SideNavigationItem { Text = "Notification", - Controller = "Account", - Action = "Notification", - IsActive = route => MatchRoute(route, "Account", "Notification"), + Controller = "Notification", + Action = "Index", + IsActive = route => MatchRoute(route, "Notification", "Index"), }, }, }, diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountEmploymentDetailsViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountEmploymentDetailsViewModel.cs new file mode 100644 index 000000000..41b93b692 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountEmploymentDetailsViewModel.cs @@ -0,0 +1,95 @@ +namespace LearningHub.Nhs.WebUI.Models.UserProfile +{ + using System; + + /// + /// Defines the . + /// + public class MyAccountEmploymentDetailsViewModel + { + /// + /// Gets or sets the Id. + /// + public int Id { get; set; } + + /// + /// Gets or sets the Country. + /// + public string Country { get; set; } + + /// + /// Gets or sets the Region. + /// + public string Region { get; set; } + + /// + /// Gets or sets the CountryName. + /// + public string CountryName { get; set; } + + /// + /// Gets or sets the RegionName. + /// + public string RegionName { get; set; } + + /// + /// Gets or sets the user employment id. + /// + public int EmploymentId { get; set; } + + /// + /// Gets or sets the job role id. + /// + public int? JobRoleId { get; set; } + + /// + /// Gets or sets the CurrentRole. + /// + public string JobRole { get; set; } + + /// + /// Gets or sets the medical council id. + /// + public int? MedicalCouncilId { get; set; } + + /// + /// Gets or sets the ProfessionalRegistrationNumber. + /// + public string MedicalCouncilNo { get; set; } + + /// + /// Gets or sets the grade id. + /// + public int? GradeId { get; set; } + + /// + /// Gets or sets the Grade. + /// + public string Grade { get; set; } + + /// + /// Gets or sets the specialty id. + /// + public int? SpecialtyId { get; set; } + + /// + /// Gets or sets the PrimarySpecialty. + /// + public string PrimarySpecialty { get; set; } + + /// + /// Gets or sets the StartDate. + /// + public DateTimeOffset? JobStartDate { get; set; } + + /// + /// Gets or sets the location id. + /// + public int LocationId { get; set; } + + /// + /// Gets or sets the PlaceOfWork. + /// + public string PlaceOfWork { get; set; } + } +} diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountPersonalDetailsViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountPersonalDetailsViewModel.cs new file mode 100644 index 000000000..05abe5e71 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountPersonalDetailsViewModel.cs @@ -0,0 +1,64 @@ +namespace LearningHub.Nhs.WebUI.Models.UserProfile +{ + using System.ComponentModel; + using System.ComponentModel.DataAnnotations; + using LearningHub.Nhs.WebUI.Attributes; + using LearningHub.Nhs.WebUI.Helpers; + + /// + /// Defines the . + /// + public class MyAccountPersonalDetailsViewModel + { + /// + /// Gets or sets the UserName. + /// + [DisplayName("Username")] + public string UserName { get; set; } + + /// + /// Gets or sets the Name. + /// + [DisplayName("Name")] + public string Name { get; set; } + + /// + /// Gets or sets the FirstName. + /// + [Required(ErrorMessage = "Enter a first name")] + [StringLength(50, MinimumLength = 1, ErrorMessage = "First name must be less than 50 characters.")] + [DisplayName("First name")] + public string FirstName { get; set; } + + /// + /// Gets or sets the LastName. + /// + [Required(ErrorMessage = "Enter a last name")] + [StringLength(50, MinimumLength = 1, ErrorMessage = "Last name must be less than 50 characters.")] + [DisplayName("Last name")] + public string LastName { get; set; } + + /// + /// Gets or sets the PreferredName. + /// + [StringLength(50, MinimumLength = 0, ErrorMessage = "Preferred name must be less than 50 characters.")] + [DisplayName("Preferred name")] + public string PreferredName { get; set; } + + /// + /// Gets or sets the Primary Email Address. + /// + [DataType(DataType.EmailAddress)] + [Required(ErrorMessage = "Enter a primary email address")] + [MaxLength(100, ErrorMessage = CommonValidationErrorMessages.TooLongEmail)] + [EmailAddress(ErrorMessage = CommonValidationErrorMessages.InvalidEmail)] + [NoWhitespace(ErrorMessage = CommonValidationErrorMessages.WhitespaceInEmail)] + [DisplayName("Primary email address")] + public string PrimaryEmailAddress { get; set; } + + /// + /// Gets or sets the new primary email address. + /// + public string NewPrimaryEmailAddress { get; set; } + } +} diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountSecurityViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountSecurityViewModel.cs new file mode 100644 index 000000000..c27467f71 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountSecurityViewModel.cs @@ -0,0 +1,45 @@ +namespace LearningHub.Nhs.WebUI.Models.UserProfile +{ + using System; + + /// + /// Defines the . + /// + public class MyAccountSecurityViewModel + { + /// + /// Gets or sets the Id. + /// + public int Id { get; set; } + + /// + /// Gets or sets the SecondaryEmailAddress. + /// + public string SecondaryEmailAddress { get; set; } + + /// + /// Gets or sets the SecurityFirstQuestion. + /// + public string SecurityFirstQuestion { get; set; } + + /// + /// Gets or sets the SecuritySecondQuestion. + /// + public string SecuritySecondQuestion { get; set; } + + /// + /// Gets or sets the LastUpdated. + /// + public DateTimeOffset LastUpdated { get; set; } + + /// + /// Gets or sets the PasswordHash. + /// + public string PasswordHash { get; set; } + + /// + /// Gets or sets the SecurityQuestionLastUpdated. + /// + public DateTimeOffset? SecurityQuestionLastUpdated { get; set; } + } +} diff --git a/LearningHub.Nhs.WebUI/Services/UserService.cs b/LearningHub.Nhs.WebUI/Services/UserService.cs index 04a2bf68f..6978f4d38 100644 --- a/LearningHub.Nhs.WebUI/Services/UserService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserService.cs @@ -1901,5 +1901,160 @@ public string Base64MD5HashDigest(string szString) return Convert.ToBase64String(abHashDigest); } + + /// + /// Get Personal details of current user for My Account. + /// + /// UserPersonalDetailsViewModel. + public async Task GetMyAccountPersonalDetailsAsync() + { + MyAccountPersonalDetailsViewModel viewModel = null; + + PersonalDetailsViewModel personalDetailsModel = await this.GetCurrentUserPersonalDetailsAsync(); + + if (personalDetailsModel != null) + { + viewModel = new MyAccountPersonalDetailsViewModel + { + UserName = personalDetailsModel.UserName, + FirstName = personalDetailsModel.FirstName, + LastName = personalDetailsModel.LastName, + PreferredName = personalDetailsModel.PreferredName, + Name = personalDetailsModel.FirstName + " " + personalDetailsModel.LastName, + PrimaryEmailAddress = personalDetailsModel.PrimaryEmailAddress, + NewPrimaryEmailAddress = personalDetailsModel.NewPrimaryEmailAddress, + }; + } + + return viewModel; + } + + /// + /// Update MyAccount Personal Details Async. + /// + /// userId. + /// personal details. + /// The . + public async Task UpdateMyAccountPersonalDetailsAsync(int userId, MyAccountPersonalDetailsViewModel model) + { + PersonalDetailsViewModel personalDetailsViewModel = new PersonalDetailsViewModel + { + UserId = userId, + FirstName = model.FirstName, + LastName = model.LastName, + PreferredName = model.PreferredName, + }; + + var json = JsonConvert.SerializeObject(personalDetailsViewModel); + var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); + + var client = await this.userApiHttpClient.GetClientAsync(); + var request = $"ElfhUser/UpdateMyAccountPersonalDetails"; + var response = await client.PutAsync(request, stringContent).ConfigureAwait(false); + + if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + if (!response.IsSuccessStatusCode) + { + throw new Exception("Update personal details failed!"); + } + } + + /// + /// Get MyEmployment Details Async. + /// + /// The . + public async Task GetMyEmploymentDetailsAsync() + { + MyAccountEmploymentDetailsViewModel viewModel = null; + + var personalDetailsModel = await this.GetCurrentUserPersonalDetailsAsync(); + var employmentViewModel = await this.GetPrimaryUserEmploymentForUser(personalDetailsModel.UserId); + + if (personalDetailsModel != null) + { + viewModel = new MyAccountEmploymentDetailsViewModel + { + Id = personalDetailsModel.UserId, + + EmploymentId = employmentViewModel.Id, + JobRoleId = employmentViewModel.JobRoleId, + MedicalCouncilId = employmentViewModel.MedicalCouncilId, + MedicalCouncilNo = employmentViewModel.MedicalCouncilNo, + GradeId = employmentViewModel.GradeId, + SpecialtyId = employmentViewModel.SpecialtyId, + JobStartDate = employmentViewModel.StartDate, + LocationId = employmentViewModel.LocationId, + }; + + if (personalDetailsModel.CountryId.HasValue) + { + var country = await this.countryService.GetByIdAsync(personalDetailsModel.CountryId.Value); + viewModel.CountryName = country.Name; + } + + if (personalDetailsModel.RegionId.HasValue) + { + var region = await this.regionService.GetByIdAsync(personalDetailsModel.RegionId.Value); + viewModel.RegionName = region.Name; + } + + if (employmentViewModel.JobRoleId.HasValue) + { + var job = await this.jobRoleService.GetByIdAsync(employmentViewModel.JobRoleId.Value); + viewModel.JobRole = job.Name; + + if (employmentViewModel.GradeId.HasValue) + { + var grades = await this.gradeService.GetGradesForJobRoleAsync(employmentViewModel.JobRoleId.Value); + var grade = grades.SingleOrDefault(g => g.Id == employmentViewModel.GradeId.Value); + viewModel.Grade = grade?.Name; + } + } + + if (employmentViewModel.SpecialtyId.HasValue) + { + var specialities = await this.specialtyService.GetSpecialtiesAsync(); + var specialty = specialities.Single(s => s.Id == employmentViewModel.SpecialtyId.Value); + viewModel.PrimarySpecialty = specialty.Name; + } + + var location = await this.locationService.GetByIdAsync(employmentViewModel.LocationId); + viewModel.PlaceOfWork = $"{location.Name}
Address: {location.Address}
Org Code: {location.NhsCode}"; + } + + return viewModel; + } + + /// + /// Get MyAccount Security Details Async. + /// + /// The . + public async Task GetMyAccountSecurityDetailsAsync() + { + MyAccountSecurityViewModel viewModel = null; + + var personalDetailsModel = await this.GetCurrentUserPersonalDetailsAsync(); + var securityQuestionsViewModel = await this.loginWizardService.GetSecurityQuestionsModel(personalDetailsModel.UserId); + + if (personalDetailsModel != null) + { + viewModel = new MyAccountSecurityViewModel + { + Id = personalDetailsModel.UserId, + SecondaryEmailAddress = personalDetailsModel.SecondaryEmailAddress, + SecurityFirstQuestion = securityQuestionsViewModel.UserSecurityQuestions.Any() ? securityQuestionsViewModel.UserSecurityQuestions.First().QuestionText : null, + SecuritySecondQuestion = (securityQuestionsViewModel.UserSecurityQuestions.Any() && securityQuestionsViewModel.UserSecurityQuestions.Count > 1) ? securityQuestionsViewModel.UserSecurityQuestions[1].QuestionText : null, + LastUpdated = personalDetailsModel.LastUpdated, + PasswordHash = personalDetailsModel.PasswordHash, + SecurityQuestionLastUpdated = securityQuestionsViewModel.UserSecurityQuestions.Any() ? securityQuestionsViewModel.UserSecurityQuestions.OrderByDescending(s => s.AmendDate).Max(s => (DateTimeOffset?)s.AmendDate) : null, + }; + } + + return viewModel; + } } } diff --git a/LearningHub.Nhs.WebUI/Styles/layout/_layout.scss b/LearningHub.Nhs.WebUI/Styles/layout/_layout.scss index bbacdc52d..f0e6df0ae 100644 --- a/LearningHub.Nhs.WebUI/Styles/layout/_layout.scss +++ b/LearningHub.Nhs.WebUI/Styles/layout/_layout.scss @@ -368,3 +368,38 @@ input:required:invalid, input:focus:invalid { i { color: #4C6272; } + +.my_account_section { + align-items: flex-start; + display: inline-flex; + justify-content: flex-start; + display: inline-flex; + gap: 32px; + padding-top: 48px; + padding-bottom: 48px; +} + +/*.my-account-page-banner { + width: 100%; + height: 208px; + background-color: $nhsuk-blue; +} + +.my-account-home-link { + color: $nhsuk-white; + font-size: 16px; + font-family: Frutiger LT Std; + font-weight: 400; + text-decoration: underline; + line-height: 24px; + word-wrap: break-word +} + +.my-account-page-heading { + color: $nhsuk-white; + font-size: 48px; + font-family: Frutiger LT Std; + font-weight: 700; + line-height: 56px; + word-wrap: break-word; +}*/ diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLocation.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLocation.cshtml new file mode 100644 index 000000000..431f23fce --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLocation.cshtml @@ -0,0 +1,194 @@ +@using LearningHub.Nhs.WebUI.Models.UserProfile +@model Tuple +@{ + ViewData["DisableValidation"] = true; + ViewData["Title"] = "My Account - Change country"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + const string professionalRegConditionalId = "professional-reg-conditional-Id"; + var optionYesSelected = true; +} +
+
+ +
+
+
+ Change country +
+ @if (Model.Item2.FilterText == null) + { +

Update your country

+ } + @if (Model.Item2.FilterText == null) + { +
+
+

+ +

+
+ @Model.Item1.CountryName +
+
+
+
+
+
+ } + else + { +

Search results for @Model.Item2.FilterText

+ } + +
+
+ @if (Model.Item2.FilterText == null || Model.Item2.Country.Count() == 0) + { +

Search for example, England

+ } +
+ + + +
+
+
+
+ @if (Model.Item2.Country != null && Model.Item2.Country.Count() > 0) + { +
+
+
+
+ +

+ Select your country +

+
+
+ @foreach (var countries in Model.Item2.Country) + { +
+ + + + +
+ } +
+ +
+
+
+
+
+ +
+ } + else + { + if (Model.Item2.FilterText != null || @errorHasOccurred) + { + @await Html.PartialAsync("SerachNoResults", Model.Item2.FilterText) + } + } +
+ +
+ +
+ +

+ How would you prefer to be contacted? +

+
+
+ Select one option +
+
+
+ + +
+
+
+ +
+ +
+
+ + +
+
+
+ +
+ +
+
+ + +
+
+
+ +
+ +
+
+
+
+ + +
+ + +
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/ChangePersonalDetails.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/ChangePersonalDetails.cshtml new file mode 100644 index 000000000..b9622ce3e --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/ChangePersonalDetails.cshtml @@ -0,0 +1,110 @@ +@model LearningHub.Nhs.WebUI.Models.UserProfile.MyAccountPersonalDetailsViewModel + +@{ + ViewData["DisableValidation"] = true; + ViewData["Title"] = "My Account - Change first name"; + var errorHasOccurred = !ViewData.ModelState.IsValid; +} +
+
+ +
+
+
+ @if (errorHasOccurred) + { + + } +
+

Update personal details

+ @*
+ First name +
*@ + + @*

first name

*@ +
+ + @* + *@ +
+
+ +
+ @*
+ Change last name +
+

Update your last name

*@ +
+ + @* + *@ +
+
+ +
+ @*
+ Change preferred name +
+

Update your preferred name

*@ +
+ + @* + *@ +
+
+ +
+ @*
+
+

Enter your new primary email address

+
+
*@ +
+ +
+
+ +
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml index 15e78e763..ac9701963 100644 --- a/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml @@ -1,336 +1,117 @@ -@model LearningHub.Nhs.WebUI.Models.UserProfile.UserProfileSummaryViewModel +@model LearningHub.Nhs.WebUI.Models.UserProfile.MyAccountPersonalDetailsViewModel @{ - ViewData["Title"] = "My account details"; + ViewData["Title"] = "Personal Details"; var isgeneralUser = User.IsInRole("BasicUser"); } -
-
-
-
-

@ViewData["Title"]

- @if (isgeneralUser) - { -
+ +
+ - - @if (this.ViewBag.CheckDetails == true) - { -
- Information: -

Please check that your details are up-to-date.

-
- } - - @if (!ViewData.ModelState.IsValid) - { - - } - -

Personal details

-
- -
-
- Username -
-
- @Model.UserName -
-
-
-
- First name -
-
- @Model.FirstName -
- -
- - Change first name - - -
- -
-
-
- Last name -
-
- @Model.LastName -
- -
- - - Change last name - - -
- -
-
-
- Preferred name -
-
- @Model.PreferredName -
- -
- - Change preferred name - - -
- -
-
- -

Location

-
- -
-
- Country -
-
- @Model.CountryName -
+
+ } -
+ @if (this.ViewBag.CheckDetails == true) + { +
+ Information: +

Please check that your details are up-to-date.

+
+ } - - Change country - + @if (!ViewData.ModelState.IsValid) + { + + } -
+ +
-
- @if (Model.CountryName == "England") - {
- Region + Username
- @Model.RegionName + @Model.UserName
- -
- - - Change region - - +
+
+
+ Name +
+
+ @Model.Name
- } - - -

Email address

-
- -
-
- Primary email address -
- @if (Model.NewPrimaryEmailAddress != "" && Model.NewPrimaryEmailAddress != null) - { -
- Email pending -
- } - else - { +
+
+ Preferred name +
- @Model.PrimaryEmailAddress + @Model.PreferredName
- } - -
- - - Change primary email - -
- -
- -
-
- Secondary email address -
-
- @Model.SecondaryEmailAddress -
- -
- - - Change secondry email - - -
- -
-
- @if (Model.NewPrimaryEmailAddress != "" && Model.NewPrimaryEmailAddress != null) - { -
-
-

- The email address @Model.NewPrimaryEmailAddress is pending validation. -
You can resend the confirmation email or cancel this email change. -

-
-
- } -

Security

-
- -
-
- Password -
-
- ************* -
- -
- - - Change password - - -
- -
- -
-
- First security question -
-
- @Model.SecurityFirstQuestion -
- -
- - - Change security question 1 - - -
- -
-
-
- Second security question -
-
- @Model.SecuritySecondQuestion -
- -
- - - Change security question 2 - - -
- -
-
+
+
+
+ Primary email address +
+ @if (Model.NewPrimaryEmailAddress != "" && Model.NewPrimaryEmailAddress != null) + { +
+ Email pending +
+ } + else + { +
+ @Model.PrimaryEmailAddress +
+ } -

Job details

-
+
-
-
Current role
-
@Model.JobRole
-
- - Change current role - -
-
+ - @if (!string.IsNullOrEmpty(Model.MedicalCouncilNo)) + @if (Model.NewPrimaryEmailAddress != "" && Model.NewPrimaryEmailAddress != null) { -
-
Professional registration number
-
@Model.MedicalCouncilNo
-
- - Change professional registration number - -
+
+
+

+ The email address @Model.NewPrimaryEmailAddress is pending validation. +
You can resend the confirmation email or cancel this email change. +

+
} - @if (!User.IsInRole("BasicUser")) - { -
-
Grade
-
@Model.Grade
-
- - Change grade - -
-
- -
-
Primary specialty
-
@Model.PrimarySpecialty
-
- - Change primary specialty - -
-
-
-
Start date
-
@Model.JobStartDate?.ToString("dd MMMM yyyy")
-
- - Change start date - -
-
+ + Update details + -
-
Place of work
-
@Html.Raw(Model.PlaceOfWork)
-
- - Change place of work - -
-
+ @if (this.ViewBag.CheckDetails == true) + { +
+ +
+ +
+
} - - - @if (this.ViewBag.CheckDetails == true) - { -
- -
- -
-
- } -
-
\ No newline at end of file +
+ +
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountSecurity.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountSecurity.cshtml new file mode 100644 index 000000000..7c9ada9ba --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountSecurity.cshtml @@ -0,0 +1,126 @@ +@model LearningHub.Nhs.WebUI.Models.UserProfile.MyAccountSecurityViewModel + +@{ + ViewData["Title"] = "Security"; +} +
+
+
+
+ @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) +
+
+ + @if (!ViewData.ModelState.IsValid) + { + + } + +

Security

+
+ +
+
+ Secondary email address +
+
+ @Model.SecondaryEmailAddress +
+ +
+ + + Change secondry email + + +
+ +
+ +
+
+ Password +
+
+ ************* +
+ +
+ + + Change password + + +
+ +
+ +
+
+ Security questions +
+
+ Set on @Model.SecurityQuestionLastUpdated +
+ +
+ + + Change security question 2 + + +
+ +
+ + @*
+
+ First security question +
+
+ @Model.SecurityFirstQuestion +
+ +
+ + + Change security question 1 + + +
+ +
+
+
+ Second security question +
+
+ @Model.SecuritySecondQuestion +
+ +
+ + + Change security question 2 + + +
+ +
*@ +
+ + @if (this.ViewBag.CheckDetails == true) + { +
+ +
+ +
+
+ } +
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/MyEmployment.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/MyEmployment.cshtml new file mode 100644 index 000000000..7ec254a33 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/MyEmployment.cshtml @@ -0,0 +1,108 @@ +@model LearningHub.Nhs.WebUI.Models.UserProfile.MyAccountEmploymentDetailsViewModel + +@{ + ViewData["Title"] = "My employment"; + var isgeneralUser = User.IsInRole("BasicUser"); +} +
+
+
+
+ @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) +
+
+ @if (!ViewData.ModelState.IsValid) + { + + } +

@ViewData["Title"]

+

Job details

+
+ +
+
Current role
+
@Model.JobRole
+
+ + @if (!string.IsNullOrEmpty(Model.MedicalCouncilNo)) + { +
+
Professional registration number
+
@Model.MedicalCouncilNo
+
+ } + @if (!User.IsInRole("BasicUser")) + { +
+
Grade
+
@Model.Grade
+
+ +
+
Primary specialty
+
@Model.PrimarySpecialty
+
+ +
+
Start date
+
@Model.JobStartDate?.ToString("dd MMMM yyyy")
+
+ +
+
Place of work
+
@Html.Raw(Model.PlaceOfWork)
+
+ } +
+ + + Update details + + +

Location

+ + +
+ +
+
+ Country +
+
+ @Model.CountryName +
+ +
+ @if (Model.CountryName == "England") + { +
+
+ Region +
+
+ @Model.RegionName +
+ +
+ } +
+ + + Update location + + + @if (this.ViewBag.CheckDetails == true) + { +
+ +
+ +
+
+ } +
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/_LeftNav.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/_LeftNav.cshtml new file mode 100644 index 000000000..40cb9bb3d --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/_LeftNav.cshtml @@ -0,0 +1,43 @@ + +@{ +} + +@section Styles { + +} + +@* *@ + +@await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) diff --git a/LearningHub.Nhs.WebUI/Views/Notification/Index.cshtml b/LearningHub.Nhs.WebUI/Views/Notification/Index.cshtml index 14b3369df..39f56672c 100644 --- a/LearningHub.Nhs.WebUI/Views/Notification/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Notification/Index.cshtml @@ -1,7 +1,9 @@ @{ ViewData["Title"] = "System notifications"; } - +
+ @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) +
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index aa019e18c..91467fe3a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index 9002a0237..48b28d9df 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -29,7 +29,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index c16540ee6..2a65ce40a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index e8ddf4415..1afc3c3a4 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -17,6 +17,7 @@ + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 3b09a079f..f7bc8f3b5 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -26,7 +26,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index 1963fd724..166b3d80a 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -7,7 +7,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index e7a054e23..a5c27d2a4 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -8,7 +8,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index 7e09ec673..3b5864543 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -8,7 +8,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 6da0f9200..7bd6a6258 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -7,7 +7,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index 024756991..697a1a16f 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -14,7 +14,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index 6b6743743..d9361f98d 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index 44f8119cb..e3d97c205 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index 73e18184e..0b2b08dbd 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -22,7 +22,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index 7b945c483..3ed284972 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -7,7 +7,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index 189b0348e..c991d2f05 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -7,7 +7,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index 090dce6fc..aa7720aa0 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -7,7 +7,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index a9b7683f7..4aa414369 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -8,7 +8,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index 71195c130..e5456904f 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -8,7 +8,7 @@ - + From b575dbe2949a6197ddb580632aaf41bdf0deda46 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Fri, 1 Aug 2025 16:44:09 +0100 Subject: [PATCH 015/124] Active menu item update --- .../Styles/nhsuk/layout.scss | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss index 1c3fe0687..0594ad9db 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss @@ -280,15 +280,37 @@ li.autosuggestion-option:last-of-type { } .side-nav__item { - padding: 12px 16px; border-bottom: 1px solid #d8dde0; + margin-bottom: 0 !important; + padding:0; } + + + +.side-nav__item:last-child { + border-bottom: none; +} + +.side-nav__item a { + font-weight: 700; + display: block; + padding: 22px 12px; +} + + + .side-nav__item--active a { - font-weight: bold; - text-decoration: underline; + text-decoration: none; + color: $nhsuk-black; } +.side-nav__item--active { + background-color: #e8f0f7; +} + + + .side-nav__toggle, .side-nav__close { display: none; From 8efff647e76d6eb389220b71c4b5c399261b7964 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 11 Aug 2025 14:23:58 +0100 Subject: [PATCH 016/124] TD-5893 & TD-5835: Recent Learning page implementation --- .../LearningHub.Nhs.AdminUI.csproj | 2 +- ...rningHub.Nhs.WebUI.AutomatedUiTests.csproj | 1 + .../Controllers/Api/MyLearningController.cs | 28 + .../Controllers/MyLearningController.cs | 348 +++++++++++- .../Controllers/MyRecentLearningController.cs | 495 ++++++++++++++++++ .../Helpers/ViewActivityHelper.cs | 97 ++++ .../Interfaces/IMyLearningService.cs | 14 + .../LearningHub.Nhs.WebUI.csproj | 2 +- .../MyLearningUserActivitiesViewModel.cs | 72 +++ .../SideMenu/SideNavigationConfiguration.cs | 18 +- .../Services/MyLearningService.cs | 64 +++ .../Styles/nhsuk/layout.scss | 384 +++++++++++++- .../Styles/nhsuk/pages/mylearning.scss | 26 + .../Views/MyLearning/Index.cshtml | 363 ++++++------- .../Views/MyLearning/Indexold.cshtml | 253 +++++++++ .../Views/MyLearning/LearningHistory.cshtml | 216 ++++++++ .../RecentMyLearningActivitiesPaging.cshtml | 30 ++ .../MyLearning/_ActivityTablePaging.cshtml | 2 +- LearningHub.Nhs.WebUI/appsettings.json | 10 +- .../LearningHub.Nhs.OpenApi.Models.csproj | 2 +- ....Nhs.OpenApi.Repositories.Interface.csproj | 1 + .../Activity/IResourceActivityRepository.cs | 16 + .../EntityFramework/LearningHubDbContext.cs | 5 + ...earningHub.Nhs.OpenApi.Repositories.csproj | 1 + .../Activity/ResourceActivityRepository.cs | 43 +- ...gHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../Services/IMoodleApiService.cs | 8 + .../Services/IMyLearningService.cs | 16 + .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- .../Services/MoodleApiService.cs | 69 +++ .../Services/MyLearningService.cs | 188 ++++++- .../Services/UserService.cs | 12 +- .../LearningHub.Nhs.OpenApi.Tests.csproj | 1 + .../Controllers/MyLearningController.cs | 26 + .../LearningHub.NHS.OpenAPI.csproj | 1 + ...ub.Nhs.ReportApi.Services.Interface.csproj | 2 +- ...ub.Nhs.ReportApi.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.ReportApi.Services.csproj | 2 +- .../LearningHub.Nhs.ReportApi.Shared.csproj | 2 +- .../LearningHub.Nhs.ReportApi.csproj | 2 +- .../LearningHub.Nhs.Api.csproj | 2 +- WebAPI/LearningHub.Nhs.API/web.config | 3 + .../LearningHub.Nhs.Api.Shared.csproj | 2 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 2 +- ...earningHub.Nhs.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.Repository.csproj | 2 +- .../LearningHub.Nhs.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Services.csproj | 2 +- ...earningHub.Nhs.Migration.ConsoleApp.csproj | 2 +- ...LearningHub.Nhs.Migration.Interface.csproj | 2 +- .../LearningHub.Nhs.Migration.Models.csproj | 2 +- ...ub.Nhs.Migration.Staging.Repository.csproj | 2 +- ...LearningHub.Nhs.Migration.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Migration.csproj | 2 +- 55 files changed, 2584 insertions(+), 275 deletions(-) create mode 100644 LearningHub.Nhs.WebUI/Controllers/MyRecentLearningController.cs create mode 100644 LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs create mode 100644 LearningHub.Nhs.WebUI/Views/MyLearning/Indexold.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/MyLearning/RecentMyLearningActivitiesPaging.cshtml diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index a0de54f8f..e5fff6967 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj index 1569f9734..536fb9b3a 100644 --- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -12,6 +12,7 @@ + diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs index bcd13c08a..64b6de28d 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs @@ -44,6 +44,34 @@ public async Task GetActivityDetailed([FromBody] MyLearningRequest return this.Ok(activity); } + /// + /// Gets the detailed activity data. + /// + /// The request model - filter settings. + /// The . + [HttpPost] + [Route("GetUserRecentMyLearningActivities")] + public async Task GetUserRecentMyLearningActivities([FromBody] MyLearningRequestModel requestModel) + { + var activity = await this.myLearningService.GetUserRecentMyLearningActivities(requestModel); + + return this.Ok(activity); + } + + /// + /// Gets the detailed activity data. + /// + /// The request model - filter settings. + /// The . + [HttpPost] + [Route("GetUserLearningHistory")] + public async Task GetUserLearningHistory([FromBody] MyLearningRequestModel requestModel) + { + var activity = await this.myLearningService.GetUserLearningHistory(requestModel); + + return this.Ok(activity); + } + /// /// Gets the played segment data for the progress modal in My Learning screen. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs index a791bd5d1..a1762ec71 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs @@ -31,7 +31,7 @@ [ServiceFilter(typeof(LoginWizardFilter))] public class MyLearningController : BaseController { - private const int MyLearningPageSize = 10; + private const int MyLearningPageSize = 5; private readonly IMyLearningService myLearningService; private readonly IResourceService resourceService; private readonly IHierarchyService hierarchyService; @@ -94,6 +94,181 @@ public static string RenderRazorViewToString(Controller controller, string viewN } } + /////// + /////// Index. + /////// + /////// learningRequest. + /////// The my learning dashboard type. + /////// IActionResult. + ////[Route("MyLearning")] + ////[Route("MyLearning/activity")] + ////[HttpGet] + ////[HttpPost] + ////public async Task Index(MyLearningViewModel learningRequest = null, string myLearningDashboard = null) + ////{ + //// var myLearningRequestModel = new MyLearningRequestModel + //// { + //// SearchText = learningRequest.SearchText?.Trim(), + //// Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + //// Take = MyLearningPageSize, + //// TimePeriod = !string.IsNullOrWhiteSpace(learningRequest.TimePeriod) ? learningRequest.TimePeriod : "allDates", + //// StartDate = learningRequest.StartDate, + //// EndDate = learningRequest.EndDate, + //// Weblink = learningRequest.Weblink, + //// File = learningRequest.File, + //// Video = learningRequest.Video, + //// Article = learningRequest.Article, + //// Case = learningRequest.Case, + //// Image = learningRequest.Image, + //// Audio = learningRequest.Audio, + //// Elearning = learningRequest.Elearning, + //// Html = learningRequest.Html, + //// Assessment = learningRequest.Assessment, + //// Complete = learningRequest.Complete, + //// Incomplete = learningRequest.Incomplete, + //// Passed = learningRequest.Passed, + //// Failed = learningRequest.Failed, + //// Downloaded = learningRequest.Downloaded, + //// Viewed = learningRequest.Viewed, + //// Launched = learningRequest.Launched, + //// CertificateEnabled = learningRequest.CertificateEnabled, + //// }; + + //// if (myLearningDashboard != null) + //// { + //// if (myLearningDashboard == "my-in-progress") + //// { + //// myLearningRequestModel.Incomplete = true; + //// myLearningRequestModel.Failed = true; + //// } + //// else if (myLearningDashboard == "my-recent-completed") + //// { + //// myLearningRequestModel.Complete = true; + //// myLearningRequestModel.Passed = true; + //// myLearningRequestModel.Downloaded = true; + //// } + //// else if (myLearningDashboard == "my-certificates") + //// { + //// myLearningRequestModel.CertificateEnabled = true; + //// myLearningRequestModel.Complete = true; + //// myLearningRequestModel.Passed = true; + //// myLearningRequestModel.Downloaded = true; + //// } + //// } + + //// switch (learningRequest.MyLearningFormActionType) + //// { + //// case MyLearningFormActionTypeEnum.NextPageChange: + //// learningRequest.CurrentPageIndex += 1; + //// myLearningRequestModel.Skip = learningRequest.CurrentPageIndex * MyLearningPageSize; + //// break; + + //// case MyLearningFormActionTypeEnum.PreviousPageChange: + //// learningRequest.CurrentPageIndex -= 1; + //// myLearningRequestModel.Skip = learningRequest.CurrentPageIndex * MyLearningPageSize; + //// break; + //// case MyLearningFormActionTypeEnum.BasicSearch: + + //// myLearningRequestModel = new MyLearningRequestModel + //// { + //// SearchText = learningRequest.SearchText?.Trim(), + //// TimePeriod = !string.IsNullOrWhiteSpace(learningRequest.TimePeriod) ? learningRequest.TimePeriod : "allDates", + //// Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + //// Take = MyLearningPageSize, + //// }; + //// break; + + //// case MyLearningFormActionTypeEnum.ApplyWeekFilter: + //// myLearningRequestModel = new MyLearningRequestModel + //// { + //// SearchText = learningRequest.SearchText?.Trim(), + //// TimePeriod = "thisWeek", + //// Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + //// Take = MyLearningPageSize, + //// }; + //// break; + //// case MyLearningFormActionTypeEnum.ApplyMonthFilter: + //// myLearningRequestModel = new MyLearningRequestModel + //// { + //// SearchText = learningRequest.SearchText?.Trim(), + //// TimePeriod = "thisMonth", + //// Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + //// Take = MyLearningPageSize, + //// }; + //// break; + + //// case MyLearningFormActionTypeEnum.ApplyTwelveMonthFilter: + //// myLearningRequestModel = new MyLearningRequestModel + //// { + //// SearchText = learningRequest.SearchText?.Trim(), + //// TimePeriod = "last12Months", + //// Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + //// Take = MyLearningPageSize, + //// }; + //// break; + + //// case MyLearningFormActionTypeEnum.ApplyMajorFilters: + //// if (learningRequest.TimePeriod == "dateRange") + //// { + //// if (!this.ModelState.IsValid) + //// { + //// break; + //// } + + //// myLearningRequestModel.TimePeriod = learningRequest.TimePeriod; + //// myLearningRequestModel.StartDate = learningRequest.GetStartDate().HasValue ? learningRequest.GetStartDate().Value : null; + //// myLearningRequestModel.EndDate = learningRequest.GetEndDate().HasValue ? learningRequest.GetEndDate().Value : null; + //// } + + //// break; + + //// case MyLearningFormActionTypeEnum.ClearAllFilters: + + //// myLearningRequestModel = new MyLearningRequestModel + //// { + //// SearchText = learningRequest.SearchText?.Trim(), + //// Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + //// TimePeriod = "allDates", + //// Take = MyLearningPageSize, + //// }; + //// break; + //// } + + //// ////var result = await this.myLearningService.GetUserRecentMyLearningActivities(myLearningRequestModel); + //// var result = await this.myLearningService.GetActivityDetailed(myLearningRequestModel); + //// var response = new MyLearningViewModel(myLearningRequestModel); + //// if (learningRequest.TimePeriod == "dateRange") + //// { + //// response.StartDay = learningRequest.StartDay; + //// response.StartMonth = learningRequest.StartMonth; + //// response.StartYear = learningRequest.StartYear; + //// response.EndDay = learningRequest.EndDay; + //// response.EndMonth = learningRequest.EndMonth; + //// response.EndYear = learningRequest.EndYear; + //// } + + //// if (result != null) + //// { + //// response.TotalCount = result.TotalCount; + //// response.Activities = result.Activities.Select(entry => new ActivityDetailedItemViewModel(entry)).ToList(); + //// if (response.Activities.Any()) + //// { + //// foreach (var activity in response.Activities) + //// { + //// if (!response.MostRecentResources.Contains(activity.ResourceId)) + //// { + //// activity.IsMostRecent = true; + //// response.MostRecentResources.Add(activity.ResourceId); + //// } + //// } + //// } + //// } + + //// response.MyLearningPaging = new MyLearningPagingModel() { CurrentPage = learningRequest.CurrentPageIndex, PageSize = MyLearningPageSize, TotalItems = response.TotalCount, HasItems = response.TotalCount > 0 }; + //// this.ViewBag.MyLearningHelpUrl = this.Settings.SupportUrls.MyLearningHelpUrl; + //// return this.View(response); + ////} + /// /// Index. /// @@ -104,7 +279,7 @@ public static string RenderRazorViewToString(Controller controller, string viewN [Route("MyLearning/activity")] [HttpGet] [HttpPost] - public async Task Index(MyLearningViewModel learningRequest = null, string myLearningDashboard = null) + public async Task Index(MyLearningUserActivitiesViewModel learningRequest = null, string myLearningDashboard = null) { var myLearningRequestModel = new MyLearningRequestModel { @@ -216,8 +391,6 @@ public async Task Index(MyLearningViewModel learningRequest = nul } myLearningRequestModel.TimePeriod = learningRequest.TimePeriod; - myLearningRequestModel.StartDate = learningRequest.GetStartDate().HasValue ? learningRequest.GetStartDate().Value : null; - myLearningRequestModel.EndDate = learningRequest.GetEndDate().HasValue ? learningRequest.GetEndDate().Value : null; } break; @@ -234,33 +407,164 @@ public async Task Index(MyLearningViewModel learningRequest = nul break; } - var result = await this.myLearningService.GetActivityDetailed(myLearningRequestModel); - var response = new MyLearningViewModel(myLearningRequestModel); - if (learningRequest.TimePeriod == "dateRange") - { - response.StartDay = learningRequest.StartDay; - response.StartMonth = learningRequest.StartMonth; - response.StartYear = learningRequest.StartYear; - response.EndDay = learningRequest.EndDay; - response.EndMonth = learningRequest.EndMonth; - response.EndYear = learningRequest.EndYear; - } + var result = await this.myLearningService.GetUserRecentMyLearningActivities(myLearningRequestModel); + var response = new MyLearningUserActivitiesViewModel(myLearningRequestModel); if (result != null) { response.TotalCount = result.TotalCount; - response.Activities = result.Activities.Select(entry => new ActivityDetailedItemViewModel(entry)).ToList(); - if (response.Activities.Any()) + response.Activities = result.Activities; + } + + response.MyLearningPaging = new MyLearningPagingModel() { CurrentPage = learningRequest.CurrentPageIndex, PageSize = MyLearningPageSize, TotalItems = response.TotalCount, HasItems = response.TotalCount > 0 }; + this.ViewBag.MyLearningHelpUrl = this.Settings.SupportUrls.MyLearningHelpUrl; + return this.View(response); + } + + /// + /// Index. + /// + /// learningRequest. + /// The my learning dashboard type. + /// IActionResult. + [Route("MyLearning/LearningHistory")] + [HttpGet] + [HttpPost] + public async Task LearningHistory(MyLearningUserActivitiesViewModel learningRequest = null, string myLearningDashboard = null) + { + var myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + TimePeriod = !string.IsNullOrWhiteSpace(learningRequest.TimePeriod) ? learningRequest.TimePeriod : "allDates", + StartDate = learningRequest.StartDate, + EndDate = learningRequest.EndDate, + Weblink = learningRequest.Weblink, + File = learningRequest.File, + Video = learningRequest.Video, + Article = learningRequest.Article, + Case = learningRequest.Case, + Image = learningRequest.Image, + Audio = learningRequest.Audio, + Elearning = learningRequest.Elearning, + Html = learningRequest.Html, + Assessment = learningRequest.Assessment, + Complete = learningRequest.Complete, + Incomplete = learningRequest.Incomplete, + Passed = learningRequest.Passed, + Failed = learningRequest.Failed, + Downloaded = learningRequest.Downloaded, + Viewed = learningRequest.Viewed, + Launched = learningRequest.Launched, + CertificateEnabled = learningRequest.CertificateEnabled, + }; + + if (myLearningDashboard != null) + { + if (myLearningDashboard == "my-in-progress") { - foreach (var activity in response.Activities) + myLearningRequestModel.Incomplete = true; + myLearningRequestModel.Failed = true; + } + else if (myLearningDashboard == "my-recent-completed") + { + myLearningRequestModel.Complete = true; + myLearningRequestModel.Passed = true; + myLearningRequestModel.Downloaded = true; + } + else if (myLearningDashboard == "my-certificates") + { + myLearningRequestModel.CertificateEnabled = true; + myLearningRequestModel.Complete = true; + myLearningRequestModel.Passed = true; + myLearningRequestModel.Downloaded = true; + } + } + + switch (learningRequest.MyLearningFormActionType) + { + case MyLearningFormActionTypeEnum.NextPageChange: + learningRequest.CurrentPageIndex += 1; + myLearningRequestModel.Skip = learningRequest.CurrentPageIndex * MyLearningPageSize; + break; + + case MyLearningFormActionTypeEnum.PreviousPageChange: + learningRequest.CurrentPageIndex -= 1; + myLearningRequestModel.Skip = learningRequest.CurrentPageIndex * MyLearningPageSize; + break; + case MyLearningFormActionTypeEnum.BasicSearch: + + myLearningRequestModel = new MyLearningRequestModel { - if (!response.MostRecentResources.Contains(activity.ResourceId)) + SearchText = learningRequest.SearchText?.Trim(), + TimePeriod = !string.IsNullOrWhiteSpace(learningRequest.TimePeriod) ? learningRequest.TimePeriod : "allDates", + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + }; + break; + + case MyLearningFormActionTypeEnum.ApplyWeekFilter: + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + TimePeriod = "thisWeek", + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + }; + break; + case MyLearningFormActionTypeEnum.ApplyMonthFilter: + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + TimePeriod = "thisMonth", + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + }; + break; + + case MyLearningFormActionTypeEnum.ApplyTwelveMonthFilter: + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + TimePeriod = "last12Months", + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + }; + break; + + case MyLearningFormActionTypeEnum.ApplyMajorFilters: + if (learningRequest.TimePeriod == "dateRange") + { + if (!this.ModelState.IsValid) { - activity.IsMostRecent = true; - response.MostRecentResources.Add(activity.ResourceId); + break; } + + myLearningRequestModel.TimePeriod = learningRequest.TimePeriod; } - } + + break; + + case MyLearningFormActionTypeEnum.ClearAllFilters: + + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + TimePeriod = "allDates", + Take = MyLearningPageSize, + }; + break; + } + + var result = await this.myLearningService.GetUserLearningHistory(myLearningRequestModel); + var response = new MyLearningUserActivitiesViewModel(myLearningRequestModel); + + if (result != null) + { + response.TotalCount = result.TotalCount; + response.Activities = result.Activities; } response.MyLearningPaging = new MyLearningPagingModel() { CurrentPage = learningRequest.CurrentPageIndex, PageSize = MyLearningPageSize, TotalItems = response.TotalCount, HasItems = response.TotalCount > 0 }; diff --git a/LearningHub.Nhs.WebUI/Controllers/MyRecentLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/MyRecentLearningController.cs new file mode 100644 index 000000000..2d909f839 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Controllers/MyRecentLearningController.cs @@ -0,0 +1,495 @@ +namespace LearningHub.Nhs.WebUI.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.Models.Report; + using LearningHub.Nhs.WebUI.Configuration; + using LearningHub.Nhs.WebUI.Extensions; + using LearningHub.Nhs.WebUI.Filters; + using LearningHub.Nhs.WebUI.Helpers; + using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.WebUI.Models; + using LearningHub.Nhs.WebUI.Models.Learning; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Mvc.ViewEngines; + using Microsoft.AspNetCore.Mvc.ViewFeatures; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + + /// + /// The MyLearningController. + /// + [Authorize] + [ServiceFilter(typeof(LoginWizardFilter))] + public class MyRecentLearningController : BaseController + { + private const int MyLearningPageSize = 10; + private readonly IMyLearningService myLearningService; + private readonly IResourceService resourceService; + private readonly IHierarchyService hierarchyService; + private readonly IUserService userService; + private readonly IPDFReportService pdfReportService; + private readonly IFileService fileService; + private readonly string filePath; + + /// + /// Initializes a new instance of the class. + /// + /// The hostingEnvironment. + /// The logger. + /// The settings. + /// The httpClientFactory. + /// myLearning service. + /// resource Service. + /// hierarchy Service. + /// user Service. + /// PDF Report Service. + /// fileService. + public MyRecentLearningController(IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, IHttpClientFactory httpClientFactory, IMyLearningService myLearningService, IResourceService resourceService, IHierarchyService hierarchyService, IUserService userService, IPDFReportService pdfReportService, IFileService fileService) + : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + { + this.myLearningService = myLearningService; + this.resourceService = resourceService; + this.userService = userService; + this.pdfReportService = pdfReportService; + this.hierarchyService = hierarchyService; + this.fileService = fileService; + this.filePath = "CatalogueImageDirectory"; + } + + /// + /// RenderRazorViewToString. + /// + /// controller. + /// viewName. + /// model. + /// Html as string. + public static string RenderRazorViewToString(Controller controller, string viewName, object model = null) + { + controller.ViewData.Model = model; + using (var sw = new StringWriter()) + { + IViewEngine viewEngine = + controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as + ICompositeViewEngine; + ViewEngineResult viewResult = viewEngine.FindView(controller.ControllerContext, viewName, false); + + ViewContext viewContext = new ViewContext( + controller.ControllerContext, + viewResult.View, + controller.ViewData, + controller.TempData, + sw, + new HtmlHelperOptions()); + viewResult.View.RenderAsync(viewContext); + return sw.GetStringBuilder().ToString(); + } + } + + /// + /// Index. + /// + /// learningRequest. + /// The my learning dashboard type. + /// IActionResult. + [Route("MyRecentLearning")] + [Route("MyRecentLearning/activity")] + [HttpGet] + [HttpPost] + public async Task Index(MyLearningViewModel learningRequest = null, string myLearningDashboard = null) + { + var myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + TimePeriod = !string.IsNullOrWhiteSpace(learningRequest.TimePeriod) ? learningRequest.TimePeriod : "allDates", + StartDate = learningRequest.StartDate, + EndDate = learningRequest.EndDate, + Weblink = learningRequest.Weblink, + File = learningRequest.File, + Video = learningRequest.Video, + Article = learningRequest.Article, + Case = learningRequest.Case, + Image = learningRequest.Image, + Audio = learningRequest.Audio, + Elearning = learningRequest.Elearning, + Html = learningRequest.Html, + Assessment = learningRequest.Assessment, + Complete = learningRequest.Complete, + Incomplete = learningRequest.Incomplete, + Passed = learningRequest.Passed, + Failed = learningRequest.Failed, + Downloaded = learningRequest.Downloaded, + Viewed = learningRequest.Viewed, + Launched = learningRequest.Launched, + CertificateEnabled = learningRequest.CertificateEnabled, + }; + + if (myLearningDashboard != null) + { + if (myLearningDashboard == "my-in-progress") + { + myLearningRequestModel.Incomplete = true; + myLearningRequestModel.Failed = true; + } + else if (myLearningDashboard == "my-recent-completed") + { + myLearningRequestModel.Complete = true; + myLearningRequestModel.Passed = true; + myLearningRequestModel.Downloaded = true; + } + else if (myLearningDashboard == "my-certificates") + { + myLearningRequestModel.CertificateEnabled = true; + myLearningRequestModel.Complete = true; + myLearningRequestModel.Passed = true; + myLearningRequestModel.Downloaded = true; + } + } + + switch (learningRequest.MyLearningFormActionType) + { + case MyLearningFormActionTypeEnum.NextPageChange: + learningRequest.CurrentPageIndex += 1; + myLearningRequestModel.Skip = learningRequest.CurrentPageIndex * MyLearningPageSize; + break; + + case MyLearningFormActionTypeEnum.PreviousPageChange: + learningRequest.CurrentPageIndex -= 1; + myLearningRequestModel.Skip = learningRequest.CurrentPageIndex * MyLearningPageSize; + break; + case MyLearningFormActionTypeEnum.BasicSearch: + + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + TimePeriod = !string.IsNullOrWhiteSpace(learningRequest.TimePeriod) ? learningRequest.TimePeriod : "allDates", + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + }; + break; + + case MyLearningFormActionTypeEnum.ApplyWeekFilter: + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + TimePeriod = "thisWeek", + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + }; + break; + case MyLearningFormActionTypeEnum.ApplyMonthFilter: + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + TimePeriod = "thisMonth", + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + }; + break; + + case MyLearningFormActionTypeEnum.ApplyTwelveMonthFilter: + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + TimePeriod = "last12Months", + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + Take = MyLearningPageSize, + }; + break; + + case MyLearningFormActionTypeEnum.ApplyMajorFilters: + if (learningRequest.TimePeriod == "dateRange") + { + if (!this.ModelState.IsValid) + { + break; + } + + myLearningRequestModel.TimePeriod = learningRequest.TimePeriod; + myLearningRequestModel.StartDate = learningRequest.GetStartDate().HasValue ? learningRequest.GetStartDate().Value : null; + myLearningRequestModel.EndDate = learningRequest.GetEndDate().HasValue ? learningRequest.GetEndDate().Value : null; + } + + break; + + case MyLearningFormActionTypeEnum.ClearAllFilters: + + myLearningRequestModel = new MyLearningRequestModel + { + SearchText = learningRequest.SearchText?.Trim(), + Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, + TimePeriod = "allDates", + Take = MyLearningPageSize, + }; + break; + } + + var result = await this.myLearningService.GetUserRecentMyLearningActivities(myLearningRequestModel); + var response = new MyLearningUserActivitiesViewModel(myLearningRequestModel); + + if (result != null) + { + response.TotalCount = result.TotalCount; + response.Activities = result.Activities; + } + + response.MyLearningPaging = new MyLearningPagingModel() { CurrentPage = learningRequest.CurrentPageIndex, PageSize = MyLearningPageSize, TotalItems = response.TotalCount, HasItems = response.TotalCount > 0 }; + this.ViewBag.MyLearningHelpUrl = this.Settings.SupportUrls.MyLearningHelpUrl; + return this.View(response); + } + + /// + /// Function to export activity report to pdf. + /// + /// myLearningRequestModel. + /// A representing the result of the asynchronous operation. + [Route("/MyLearning/ExportToPDF")] + [HttpPost] + public async Task ExportToPDF(MyLearningRequestModel myLearningRequestModel) + { + var filter = myLearningRequestModel; + filter.Skip = 0; + filter.Take = 999; + var userDetails = await this.userService.GetCurrentUserBasicDetailsAsync(); + var response = new MyLearningViewModel(); + var result = await this.myLearningService.GetActivityDetailed(filter); + if (result != null) + { + response.TotalCount = result.TotalCount; + response.Activities = result.Activities.Select(entry => new ActivityDetailedItemViewModel(entry)).ToList(); + if (response.Activities.Any()) + { + foreach (var activity in response.Activities) + { + if (!response.MostRecentResources.Contains(activity.ResourceId)) + { + activity.IsMostRecent = true; + response.MostRecentResources.Add(activity.ResourceId); + } + } + } + } + + Tuple modelData = Tuple.Create(userDetails, response); + var renderedViewHTML = RenderRazorViewToString(this, "ExportToPDF", modelData); + ReportStatusModel reportStatusModel = new ReportStatusModel(); + var pdfReportResponse = await this.pdfReportService.PdfReport(renderedViewHTML, userDetails.Id); + if (pdfReportResponse != null) + { + do + { + reportStatusModel = await this.pdfReportService.PdfReportStatus(pdfReportResponse); + } + while (reportStatusModel.Id == 1); + + var pdfReportFile = await this.pdfReportService.GetPdfReportFile(pdfReportResponse); + if (pdfReportFile != null) + { + var fileName = "ActivityReport.pdf"; + return this.File(pdfReportFile, FileHelper.GetContentTypeFromFileName(fileName), fileName); + } + } + + return this.View(new Tuple(userDetails, response)); + } + + /// + /// Gets the played segment data for the progress modal in My Learning screen. + /// + /// The resourceId. + /// The resourceVersionId. + /// The majorVersion. + /// The max time. + /// Return url. + /// The . + [HttpGet] + [Route("my-learning/activity/{resourceId}/view-progress")] + public async Task ViewProgress(int resourceId, int resourceReferenceId, int version, long maxTime, string returnUrl = "/") + { + this.ViewBag.ReturnUrl = returnUrl; + var playedSegments = await this.myLearningService.GetPlayedSegments(resourceId, version); + + var allSegments = new List(); + var currentTime = 0; + var mediaLengthInSeconds = maxTime / 1000M; + + foreach (var segment in playedSegments.OrderBy(p => p.SegmentStartTime)) + { + if (segment.SegmentStartTime > currentTime) + { + allSegments.Add(new ResourcePlayedSegment + { + SegmentStartTime = currentTime, + SegmentEndTime = segment.SegmentStartTime, + Played = false, + }); + } + + allSegments.Add(new ResourcePlayedSegment + { + SegmentStartTime = segment.SegmentStartTime, + SegmentEndTime = segment.SegmentEndTime, + Played = true, + }); + + currentTime = segment.SegmentEndTime; + } + + if (currentTime < mediaLengthInSeconds) + { + allSegments.Add(new ResourcePlayedSegment + { + SegmentStartTime = currentTime, + SegmentEndTime = (int)mediaLengthInSeconds, + Played = false, + }); + } + + allSegments.ForEach(s => s.Percentage = Math.Round((s.SegmentEndTime - s.SegmentStartTime) / mediaLengthInSeconds * 100, 2)); + + var vm = new ActivityViewProgress + { + ResourceReferenceId = resourceReferenceId, + Segments = allSegments, + MediaLength = ResourcePlayedSegment.GetDurationHhmmss((int)mediaLengthInSeconds), + }; + + return this.View(vm); + } + + /// + /// Gets the certificate details of an activity. + /// + /// The resourceReferenceId. + /// The majorVersion. + /// The minorVersion. + /// The user Id. + /// The downloadCert flag. + /// The . + [HttpGet] + [Route("mylearning/certificate/{resourceReferenceId}")] + [Route("mylearning/certificate/{resourceReferenceId}/{userId}")] + [Route("mylearning/certificate/{*path}")] + public async Task GetCertificateDetails(int resourceReferenceId, int? majorVersion = 0, int? minorVersion = 0, int? userId = 0, bool downloadCert = false) + { + CertificateDetails certificateDetails = null; + string base64Image = string.Empty; + var activity = await this.myLearningService.GetResourceCertificateDetails(resourceReferenceId, majorVersion, minorVersion, userId); + if (activity.Item1 > 0 && activity.Item2 != null && ViewActivityHelper.CanDownloadCertificate(new ActivityDetailedItemViewModel(activity.Item2))) + { + var resource = await this.resourceService.GetItemByIdAsync(resourceReferenceId); + var nodePathNodes = await this.hierarchyService.GetNodePathNodes(resource.NodePathId); + var currentUser = await this.userService.GetUserByUserIdAsync((userId == 0) ? this.CurrentUserId : (int)userId); + var userEmployment = await this.userService.GetUserEmploymentByIdAsync(currentUser.PrimaryUserEmploymentId ?? 0); + if (activity.Item2.CertificateUrl != null && downloadCert) + { + var file = await this.fileService.DownloadFileAsync(this.filePath, activity.Item2.CertificateUrl); + if (file != null) + { + byte[] imageArray = new BinaryReader(file.Content).ReadBytes((int)file.ContentLength); + base64Image = Convert.ToBase64String(imageArray); + } + } + + certificateDetails = new CertificateDetails { AccessCount = activity.Item1, ProfessionalRegistrationNumber = userEmployment?.MedicalCouncilNo, NodeViewModels = nodePathNodes, UserViewModel = currentUser, ResourceItemViewModel = resource, ActivityDetailedItemViewModel = new ActivityDetailedItemViewModel(activity.Item2), DownloadCertificate = downloadCert, CertificateBase64Image = base64Image }; + } + + return this.View("LearningCertificate", certificateDetails); + } + + /// + /// Gets the certificate details of an activity. + /// + /// The resourceReferenceId. + /// The majorVersion. + /// The minorVersion. + /// The userId. + /// The . + [HttpPost] + [Route("mylearning/downloadcertificate")] + public async Task DownloadCertificate(int resourceReferenceId, int? majorVersion = 0, int? minorVersion = 0, int? userId = 0) + { + CertificateDetails certificateDetails = null; + string base64Image = string.Empty; + var activity = await this.myLearningService.GetResourceCertificateDetails(resourceReferenceId, majorVersion, minorVersion, userId); + if (activity.Item1 > 0 && activity.Item2 != null && ViewActivityHelper.CanDownloadCertificate(new ActivityDetailedItemViewModel(activity.Item2))) + { + var resource = await this.resourceService.GetItemByIdAsync(resourceReferenceId); + var nodePathNodes = await this.hierarchyService.GetNodePathNodes(resource.NodePathId); + var currentUser = await this.userService.GetUserByUserIdAsync((userId == 0) ? this.CurrentUserId : (int)userId); + var userEmployment = await this.userService.GetUserEmploymentByIdAsync(currentUser.PrimaryUserEmploymentId ?? 0); + var resourceItemUrl = this.Settings.LearningHubWebUiUrl.Trim() + "Resource/" + resourceReferenceId + "/Item"; + if (activity.Item2.CertificateUrl != null) + { + var file = await this.fileService.DownloadFileAsync(this.filePath, activity.Item2.CertificateUrl); + if (file != null) + { + byte[] imageArray = new BinaryReader(file.Content).ReadBytes((int)file.ContentLength); + base64Image = Convert.ToBase64String(imageArray); + } + } + + certificateDetails = new CertificateDetails { AccessCount = activity.Item1, ProfessionalRegistrationNumber = userEmployment?.MedicalCouncilNo, NodeViewModels = nodePathNodes, UserViewModel = currentUser, ResourceItemViewModel = resource, ActivityDetailedItemViewModel = new ActivityDetailedItemViewModel(activity.Item2), DownloadCertificate = true, CertificateBase64Image = base64Image, PdfResoureItemUrl = resourceItemUrl }; + var renderedViewHTML = new List(); + certificateDetails.PageNo++; + renderedViewHTML.Add(RenderRazorViewToString(this, "LearningCertificate", certificateDetails)); + certificateDetails.PageNo++; + renderedViewHTML.Add(RenderRazorViewToString(this, "LearningCertificate", certificateDetails)); + + ReportStatusModel reportStatusModel = new ReportStatusModel(); + var pdfReportResponse = await this.pdfReportService.PdfReport(renderedViewHTML, currentUser.Id); + if (pdfReportResponse != null) + { + do + { + reportStatusModel = await this.pdfReportService.PdfReportStatus(pdfReportResponse); + } + while (reportStatusModel.Id == 1); + + var pdfReportFile = await this.pdfReportService.GetPdfReportFile(pdfReportResponse); + if (pdfReportFile != null) + { + string fileName = this.GenerateCertificateName(certificateDetails.ActivityDetailedItemViewModel.Title); + return this.File(pdfReportFile, FileHelper.GetContentTypeFromFileName(fileName), fileName); + } + } + } + + return this.View("LearningCertificate", certificateDetails); + } + + /// + /// Gets the Certificate name. + /// + /// The resourceTitile. + /// The . + private string GenerateCertificateName(string resourceTitile) + { + if (!string.IsNullOrEmpty(resourceTitile)) + { + if (resourceTitile.Length <= 71) + { + string filename = "LH_Certificate_" + resourceTitile + ".pdf"; + return filename; + } + else if (resourceTitile.Length > 71) + { + string filename = "LH_Certificate_" + resourceTitile.Truncate(67, true) + "_ " + ".pdf"; + return filename; + } + } + + return "LearningCertificate.pdf"; + } + } +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs index f3bd9259c..3deeb1b56 100644 --- a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.WebUI.Models; /// @@ -71,6 +72,102 @@ public static string GetResourceTypeText(this ActivityDetailedItemViewModel acti return typeText; } + /// + /// Get resource type details. + /// + /// resourceType. + /// The string. + public static string GetResourceTypeDesc(ResourceTypeEnum resourceType) + { + string typeText = string.Empty; + switch (resourceType) + { + case ResourceTypeEnum.Assessment: + typeText = "Assessment"; + break; + case ResourceTypeEnum.Article: + typeText = "Article"; + break; + case ResourceTypeEnum.Audio: + typeText = "Audio"; + break; + case ResourceTypeEnum.GenericFile: + typeText = "File"; + break; + case ResourceTypeEnum.Image: + typeText = "Image"; + break; + case ResourceTypeEnum.Scorm: + typeText = "elearning"; + break; + case ResourceTypeEnum.Video: + typeText = "Video"; + break; + case ResourceTypeEnum.WebLink: + typeText = "Web link"; + break; + case ResourceTypeEnum.Case: + typeText = "Case"; + break; + case ResourceTypeEnum.Html: + typeText = "HTML"; + break; + case ResourceTypeEnum.Moodle: + typeText = "Course"; + break; + default: + typeText = string.Empty; + break; + } + + return typeText; + } + + /// + /// GetActivityStatusDisplayText. + /// + /// The activity. + /// The string. + public static string GetActivityStatusDisplayText(MyLearningCombainedActivitiesViewModel activity) + { + if (activity.ActivityStatus == ActivityStatusEnum.Completed || activity.ActivityStatus == ActivityStatusEnum.Passed || activity.ActivityStatus == ActivityStatusEnum.Passed || activity.ActivityStatus == ActivityStatusEnum.Viewed || activity.ActivityStatus == ActivityStatusEnum.Downloaded) + { + return "Completed"; + } + else + { + return "InProgress"; + } + } + + /// + /// CanDownloadCertificate. + /// + /// The activityDetailedItemViewModel. + /// The bool. + public static bool CanCertificateawarded(this MyLearningCombainedActivitiesViewModel activitiesViewModel) + { + if (activitiesViewModel.CertificateEnabled == true) + { + if (activitiesViewModel.ResourceType == ResourceTypeEnum.Scorm) + { + if (GetActivityStatusDisplayText(activitiesViewModel) == "Completed" || GetActivityStatusDisplayText(activitiesViewModel) == "Passed") + { + return true; + } + } + else + { + if (GetActivityStatusDisplayText(activitiesViewModel) == "Completed" || GetActivityStatusDisplayText(activitiesViewModel) == "Passed" || GetActivityStatusDisplayText(activitiesViewModel) == "Downloaded") + { + return true; + } + } + } + + return false; + } + /// /// GetResourceTypeVerb. /// diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs index 726f78477..773b4791f 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs @@ -17,6 +17,20 @@ public interface IMyLearningService /// The . Task GetActivityDetailed(MyLearningRequestModel requestModel); + /// + /// Gets the user recent my leraning activities.. + /// + /// The request model. + /// The . + Task GetUserRecentMyLearningActivities(MyLearningRequestModel requestModel); + + /// + /// Gets the user leraning history. + /// + /// The request model. + /// The . + Task GetUserLearningHistory(MyLearningRequestModel requestModel); + /// /// Gets the played segment data for the progress modal in My Learning screen. /// diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index b69ead582..a65aa93dd 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -113,7 +113,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs b/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs new file mode 100644 index 000000000..7b8ec1a39 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs @@ -0,0 +1,72 @@ +namespace LearningHub.Nhs.WebUI.Models +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Linq; + using System.Reflection; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.Models.Paging; + using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.WebUI.Helpers; + using LearningHub.Nhs.WebUI.Models.Learning; + using NHSUKViewComponents.Web.ViewModels; + + /// + /// Defines the . + /// + public class MyLearningUserActivitiesViewModel : MyLearningRequestModel + { + /// + /// Initializes a new instance of the class. + /// + public MyLearningUserActivitiesViewModel() + { + this.Activities = new List(); + } + + /// + /// Initializes a new instance of the class. + /// + /// MyLearningRequestModel. + public MyLearningUserActivitiesViewModel(MyLearningRequestModel requestModel) + { + this.Activities = new List(); + foreach (PropertyInfo prop in requestModel.GetType().GetProperties()) + { + this.GetType().GetProperty(prop.Name).SetValue(this, prop.GetValue(requestModel, null), null); + } + } + + /// + /// Gets or sets the learning form event. + /// + public MyLearningFormActionTypeEnum MyLearningFormActionType { get; set; } + + /// + /// Gets or sets the page item index. + /// + public int CurrentPageIndex { get; set; } = 0; + + /// + /// Gets or sets the TotalCount. + /// + public int TotalCount { get; set; } + + /// + /// Gets or sets the MostRecentResources. + /// + public List MostRecentResources { get; set; } + + /// + /// Gets or sets the Activities. + /// + public List Activities { get; set; } + + /// + /// Gets or sets the learning result paging. + /// + public PagingViewModel MyLearningPaging { get; set; } + } +} diff --git a/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs b/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs index 9f95fc173..6b7801793 100644 --- a/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs +++ b/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs @@ -60,31 +60,31 @@ public static IEnumerable GetGroupedMenus() { new SideNavigationItem { - Text = "Recent", - Controller = "Activity", - Action = "Recent", + Text = "Recent learning", + Controller = "MyLearning", + Action = "Index", IsActive = route => MatchRoute(route, "Activity", "Recent"), }, new SideNavigationItem { - Text = "Bookmark", - Controller = "Activity", + Text = "Bookmarks", + Controller = "MyLearning", Action = "Bookmark", IsActive = route => MatchRoute(route, "Activity", "Bookmark"), }, new SideNavigationItem { Text = "Certificates", - Controller = "Activity", + Controller = "MyLearning", Action = "Certificates", IsActive = route => MatchRoute(route, "Activity", "Certificates"), }, new SideNavigationItem { Text = "Learning history", - Controller = "Activity", - Action = "Learninghistory", - IsActive = route => MatchRoute(route, "Activity", "Learninghistory"), + Controller = "MyLearning", + Action = "LearningHistory", + IsActive = route => MatchRoute(route, "Activity", "LearningHistory"), }, }, }, diff --git a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs index 1e1662201..df38b15ef 100644 --- a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs +++ b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs @@ -59,6 +59,70 @@ public async Task GetActivityDetailed(MyLearningReq return viewModel; } + /// + /// Gets the user recent my leraning activities. + /// + /// The request model. + /// The . + public async Task GetUserRecentMyLearningActivities(MyLearningRequestModel requestModel) + { + MyLearningActivitiesDetailedViewModel viewModel = null; + + var json = JsonConvert.SerializeObject(requestModel); + var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); + + var client = await this.OpenApiHttpClient.GetClientAsync(); + + var request = $"MyLearning/GetUserRecentMyLearningActivities"; + var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewModel = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewModel; + } + + /// + /// Gets the user recent my leraning activities. + /// + /// The request model. + /// The . + public async Task GetUserLearningHistory(MyLearningRequestModel requestModel) + { + MyLearningActivitiesDetailedViewModel viewModel = null; + + var json = JsonConvert.SerializeObject(requestModel); + var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); + + var client = await this.OpenApiHttpClient.GetClientAsync(); + + var request = $"MyLearning/GetUserLearningHistory"; + var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewModel = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewModel; + } + /// /// Gets the played segment data for the progress modal in My Learning screen. /// diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss index c12618c2a..4448f4ee9 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss @@ -1,3 +1,4 @@ +@use "../abstracts/all" as *; @use "nhsuk" as *; body { @@ -54,12 +55,91 @@ body { max-width: none } - - .app-main-wrapper--no-padding { padding: 0 } +.nhsuk-header__container.app-width-container { + display: flex; + justify-content: space-between; + gap: 0 px2rem(24); + padding: px2rem(16) 0; +} + +.nhsuk-header__content { + display: flex; + align-items: center; + min-height: px2rem(40); + margin-left: auto; +} + +.nhsuk-header__logo { + flex: 1 0 0; +} + +.nhsuk-header__logo .nhsuk-header__link--service { + display: inline-flex; +} + +.nhsuk-header__service-name { + font-size: px2rem(19); +} + +.nhsuk-account__login { + font-size: px2rem(14); + float: right; + position: relative; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; + gap: px2rem(24); +} + +.nhsuk-header__notification-dot { + position: absolute; + top: px2rem(8); + right: px2rem(-10); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: white; + min-width: px2rem(18); + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + padding: px2rem(1) px2rem(3) 0; +} + +.nhsuk-header__menu { + display: none; +} + +.nhsuk-header__search .nhsuk-search__input { + width: px2rem(260); +} + +.nhsuk-header__search { + .nhsuk-search__input { + width: px2rem(260); + + &::-moz-placeholder { + opacity: 1; + } + } + + #search > label.nhsuk-u-visually-hidden { + background-color: $nhsuk-white; + } +} + +.nhsuk-account__login--link, +.nhsuk-account__login--link:visited, +.nhsuk-account__login--link:hover { + color: #fff; +} + .beta-banner { background-color: $color_nhsuk-grey-5; color: $nhsuk-text-color; @@ -90,6 +170,42 @@ body { font-size: px2rem(16); } +.nhsuk-footer { + padding: px2rem(48) 0; +} + + +#header-dropdown-menu-control { + opacity: 0; + position: absolute; +} + +#header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { + display: block +} + +#header-mobile-search-control { + display: none; +} + +.nhsuk-header__break { + display: none; +} + +.nhsuk-header__mobile-only-nav { + display: none; +} + +.nhsuk-header__mobile-break { + display: none; +} + +.nhsuk-header__navigation-item--current { + a { + font-weight: bold; + } +} + button[data-toggle="modal"] { color: #005eb8; padding: 0; @@ -154,6 +270,7 @@ li.autosuggestion-option:last-of-type { border-bottom: none !important; } + /* side navigation styles */ .side-nav__list { list-style: none; @@ -163,15 +280,37 @@ li.autosuggestion-option:last-of-type { } .side-nav__item { - padding: 12px 16px; border-bottom: 1px solid #d8dde0; + margin-bottom: 0 !important; + padding: 0; +} + + + + +.side-nav__item:last-child { + border-bottom: none; } +.side-nav__item a { + font-weight: 700; + display: block; + padding: 22px 12px; +} + + + .side-nav__item--active a { - font-weight: bold; - text-decoration: underline; + text-decoration: none; + color: $nhsuk-black; +} + +.side-nav__item--active { + background-color: #e8f0f7; } + + .side-nav__toggle, .side-nav__close { display: none; @@ -207,7 +346,7 @@ li.autosuggestion-option:last-of-type { width: 80%; max-width: 320px; height: 100%; - background-color: #e8f0f7; + background-color: #e8f0f7; border-right: 40px solid #005eb8; box-sizing: border-box; transform: translateX(-100%); @@ -222,8 +361,8 @@ li.autosuggestion-option:last-of-type { .side-nav__close { position: relative; - right: -35px; - z-index: 2; + right: -35px; + z-index: 2; align-self: flex-end; text-align: right; } @@ -279,12 +418,141 @@ li.autosuggestion-option:last-of-type { /* small desktop */ @media (max-width: px2rem(989)) { + + .nhsuk-header__container { + flex-wrap: wrap; + } + + .nhsuk-header { + padding: 0; + } + + .nhsuk-header__container.app-width-container { + flex-wrap: wrap; + gap: 0 0; + padding: px2rem(16) px2rem(32); + } + + .nhsuk-header__link--service { + align-items: center; + -ms-flex-align: center; + margin-bottom: 0; + width: auto; + } + + .nhsuk-header__service-name { + padding-left: px2rem(16); + } + + .nhsuk-header__logo { + order: 0; + } + + .nhsuk-account__login { + order: 1; + margin-left: auto; + margin-right: 0px; + } + + .nhsuk-header__break { + display: block; + width: 100%; + height: px2rem(24); + order: 2 + } + + .nhsuk-header__search { + order: 3; + flex-grow: 1; + margin-left: 0; + margin-right: px2rem(24); + } + + .nhsuk-header__menu { + display: block; + position: relative; + order: 4; + flex: 0 0 px2rem(74); + } + + .nhsuk-header__navigation-list .nhsuk-header__navigation-item, + .nhsuk-header__navigation .nhsuk-header__navigation-title { + border-top: 1px solid $color_nhsuk-grey-4; + } + + .nhsuk-header__menu-toggle { + text-align: center; + margin: 0; + right: 0; + font-weight: 600; + } + + .nhsuk-header__search-form { + display: flex; + } + + .nhsuk-header__search .nhsuk-search__input { + flex: 1 0 0; + } + + #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { + display: block; + } + + .nhsuk-header__notification-dot { + position: absolute; + top: px2rem(15); + left: px2rem(115); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: $nhsuk-white; + min-width: px2rem(18); + width: fit-content; + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + } + + .nhsuk-header__navigation-item--current .nhsuk-header__notification-dot { + left: px2rem(125); + } + + .nhsuk-header__menu-notification-dot { + position: absolute; + top: px2rem(-5); + right: px2rem(-6); + background: $nhsuk-error-color; + width: px2rem(12); + height: px2rem(12); + border-radius: px2rem(6); + box-shadow: 0 0 0 2px white; + z-index: 10; + } + .autosuggestion-menu { top: 100%; } } + /* tablet */ @media (max-width: px2rem(768)) { + + .nhsuk-width-container.app-width-container, + .nhsuk-width-container.app-width-container.beta-banner { + padding-left: px2rem(16); + padding-right: px2rem(16); + } + + .nhsuk-back-link { + padding: 0.5rem 0; + } + + .nhsuk-header__menu .nhsuk-header__not-mobile { + display: none; + } + .autosuggestion-menu { top: 100%; } @@ -390,12 +658,102 @@ li.autosuggestion-option:last-of-type { /* mobile */ @media (max-width: px2rem(640)) { - .autosuggestion-menu { - top: 100%; - } -} + .nhsuk-header__not-mobile { + display: none; + } + .nhsuk-header__logo { + max-width: none; + } + + .nhsuk-header__mobile-only-nav { + display: flex; + order: 1; + justify-content: space-around; + gap: 0 px2rem(16); + align-items: flex-start; + flex-wrap: wrap; + width: px2rem(166); + } + + .nhsuk-header__pre-login .nhsuk-header__mobile-only-nav { + align-items: center; + justify-content: flex-end; + padding-bottom: px2rem(16); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__menu { + margin-right: px2rem(12); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { + margin-left: px2rem(12); + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-break { + display: block; + width: 100%; + height: 0; + } + + .nhsuk-header__link--service { + flex-direction: column; + align-items: flex-start; + } + + .nhsuk-header__notification-dot { + top: px2rem(13); + left: px2rem(100); + } + + .nhsuk-header__service-name { + padding: px2rem(12) 0 0; } -} + .nhsuk-header__search-toggle { + position: relative; + height: px2rem(40); + order: 2; + padding: px2rem(7) px2rem(10) 0; + margin: 0 + } + + .nhsuk-header__search .nhsuk-search__submit { + padding-top: nhsuk-spacing(1); + } + + .nhsuk-header__menu { + order: 3; + } + + .nhsuk-header__search { + order: 4; + width: 100%; + flex-grow: 1; + margin: px2rem(16) px2rem(-16) 0; + border-bottom: 1px solid $color_nhsuk-grey-4; + } + + #header-mobile-search-control { + display: block; + opacity: 0; + position: absolute; + } + + #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { + display: block; + } + + .nhsuk-width-container.nhsuk-header__container.app-width-container { + padding-bottom: 0; + } + + .autosuggestion-menu { + top: 100%; + } +} diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/mylearning.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/mylearning.scss index bebb4e2f8..46bf7eb76 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/mylearning.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/mylearning.scss @@ -3,6 +3,32 @@ .my-learning { + .nhs-progress-container { + display: flex; + height: 10px; + overflow: hidden; + line-height: 0; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; + } + + .nhs-progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: $nhsuk-green; + transition: width .6s ease; + } + .nhs-item-row { + display: flex; + justify-content: space-between; + align-items: center; + } .downloadbuttonaslink { background: none !important; diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml index 6bbc6c8d6..93df45e8e 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml @@ -1,23 +1,20 @@ @using LearningHub.Nhs.Models.Enums +@using LearningHub.Nhs.WebUI.Helpers @using LearningHub.Nhs.WebUI.Models -@using LearningHub.Nhs.WebUI.Models.Learning -@model MyLearningViewModel; - +@inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; +@model MyLearningUserActivitiesViewModel; @{ - - ViewData["Title"] = "My learning"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var startHintTextLines = new List { $"From" }; - var endHintTextLines = new List { $"To" }; - var routeData = ViewActivityHelper.GetActivityParameters(Model); + var returnUrl = $"{Context.Request.Path}{Context.Request.QueryString}"; + string GetMoodleCourseUrl(int courseId) + { + return moodleApiService.GetCourseUrl(courseId); + } } - @section styles { } - @section NavBreadcrumbs {
@@ -38,210 +35,182 @@
} +
-
-
- @if (errorHasOccurred) - { - - } - @*

My learning

*@ -

- You can use this page to search and filter learning resources you've accessed, download certificates and generate a report - of your activity. -

- -
- - - Learn how to manage My learning - - -
-

My learning displays an itemised activity table. This table details everything you have accessed in the Learning Hub.

-

You can search your learning activity by entering a search term in the search box. This will only search your learning activity and not the entire Learning Hub. Results will be displayed placing your search term as the filter. To return to your full Itemised activity, either select clear all filters or uncheck your search term(s).

-
-
- -

- Download a report of your learning. -

- @if (Model.TotalCount != 0) - { -
- -
- } - else - { - - } - -
- -

Search within My learning

-
- - - - -
-

-

@Model.TotalCount activity result@(Model.TotalCount > 1 ? "s" : "")

-

-
+
+ +
+ @await Component.InvokeAsync("SideNav", new { groupTitle = "Activity" })
- -
-
-
- -
- -
-
- - - - Filter results - - -
- - -
-
- -
- -
-
+ +
+
+

Recent learning

+
+
+ +
+
+
+
+
+ @foreach (var activity in Model.Activities) + { + + var isCompleted = ViewActivityHelper.GetActivityStatusDisplayText(activity) == ActivityStatusEnum.Completed.ToString(); + + var typeLabelClass = isCompleted ? "nhsuk-u-primary-text-color" : "nhsuk-u-secondary-text-color"; + var accessedLabelClass = typeLabelClass; + var tagColorClass = isCompleted ? "nhsuk-tag--green" : "nhsuk-tag--blue"; + var statusText = isCompleted ? "Completed" : "In progress"; + var displayText = isCompleted ? "Completed" : "Accessed"; + + var activityDate = activity.ActivityDate.Date; + var today = DateTime.Today; + var dateTimeText = activityDate == today ? "Today" + : activityDate == today.AddDays(-1) ? "Yesterday" + : activityDate.ToString("dd MMM yyyy"); + +
+ + @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { + @activity.Title + } + else + { + @activity.Title + } + +
+
+
+ Type: + + + @ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) + + + + @displayText: + + + + @dateTimeText +
-
-
-
-
- -
- - -
-
- -
-
-
- -
-
- -
+
+ + + @statusText + +
-
- - -
- -
-
- + @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { +
+
-
+ } + + + @if (ViewActivityHelper.CanCertificateawarded(activity)) + { + +
+ + + + + + + + + + + + + + + Certificate: + awarded [@dateTimeText] -
-
-
-
-
-
- + } + else + { +
+ @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { +
@activity.TotalActivities of @activity.CompletedActivities activities completed
+ } + else + { +
+ No certificate available +
+ } + @if (activity.CertificateEnabled == true) + { +
+ Includes a certificate + + + + + + + + + + + + + + + +
+ }
-
-
- + }
- -
-
+
+
+
+
+ }
- -
-
-
-
-

Get help

-

- Find further guidance and support on how to manage My learning at the Learning Hub help centre. -

-
+
+ @await Html.PartialAsync("_ActivityTablePaging", Model) +
Not seeing what you are looking for? Go to learning history
-
- @await Html.PartialAsync("_ActivityTablePaging", Model) -
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/Indexold.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/Indexold.cshtml new file mode 100644 index 000000000..0878d6d06 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/Indexold.cshtml @@ -0,0 +1,253 @@ +@using LearningHub.Nhs.Models.Enums +@using LearningHub.Nhs.WebUI.Models +@using LearningHub.Nhs.WebUI.Models.Learning +@model MyLearningViewModel; + +@{ + + ViewData["Title"] = "My learning"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var startHintTextLines = new List { $"From" }; + var endHintTextLines = new List { $"To" }; + var routeData = ViewActivityHelper.GetActivityParameters(Model); +} + +@section styles { + + + +} + +@section NavBreadcrumbs { + +
+
+
+
+ +
+ Home + +
+
+

My learning activity

+
+
+
+
+
+} + +
+
+
+
+ @if (errorHasOccurred) + { + + } + @*

My learning

*@ +

+ You can use this page to search and filter learning resources you've accessed, download certificates and generate a report + of your activity. +

+ +
+ + + Learn how to manage My learning + + +
+

My learning displays an itemised activity table. This table details everything you have accessed in the Learning Hub.

+

You can search your learning activity by entering a search term in the search box. This will only search your learning activity and not the entire Learning Hub. Results will be displayed placing your search term as the filter. To return to your full Itemised activity, either select clear all filters or uncheck your search term(s).

+
+
+ +

+ Download a report of your learning. +

+ @if (Model.TotalCount != 0) + { +
+ +
+ } + else + { + + } + +
+ +

Search within My learning

+
+ + + + +
+

+

@Model.TotalCount activity result@(Model.TotalCount > 1 ? "s" : "")

+

+
+
+ +
+
+
+ +
+ +
+
+ + + + Filter results + + +
+ + +
+
+ +
+ +
+
+ + +
+
+
+
+
+ +
+ + +
+
+ +
+
+
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+

Get help

+

+ Find further guidance and support on how to manage My learning at the Learning Hub help centre. +

+
+
+
+
+ @await Html.PartialAsync("_ActivityTablePaging", Model) +
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml new file mode 100644 index 000000000..93df45e8e --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml @@ -0,0 +1,216 @@ +@using LearningHub.Nhs.Models.Enums +@using LearningHub.Nhs.WebUI.Helpers +@using LearningHub.Nhs.WebUI.Models +@inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; +@model MyLearningUserActivitiesViewModel; +@{ + var returnUrl = $"{Context.Request.Path}{Context.Request.QueryString}"; + string GetMoodleCourseUrl(int courseId) + { + return moodleApiService.GetCourseUrl(courseId); + } +} +@section styles { + + + +} +@section NavBreadcrumbs { + +
+
+
+
+ +
+ Home + +
+
+

My learning activity

+
+
+
+
+
+} + + +
+
+
+ +
+ @await Component.InvokeAsync("SideNav", new { groupTitle = "Activity" }) +
+ + +
+
+

Recent learning

+
+
+ +
+
+
+
+
+ @foreach (var activity in Model.Activities) + { + + var isCompleted = ViewActivityHelper.GetActivityStatusDisplayText(activity) == ActivityStatusEnum.Completed.ToString(); + + var typeLabelClass = isCompleted ? "nhsuk-u-primary-text-color" : "nhsuk-u-secondary-text-color"; + var accessedLabelClass = typeLabelClass; + var tagColorClass = isCompleted ? "nhsuk-tag--green" : "nhsuk-tag--blue"; + var statusText = isCompleted ? "Completed" : "In progress"; + var displayText = isCompleted ? "Completed" : "Accessed"; + + var activityDate = activity.ActivityDate.Date; + var today = DateTime.Today; + var dateTimeText = activityDate == today ? "Today" + : activityDate == today.AddDays(-1) ? "Yesterday" + : activityDate.ToString("dd MMM yyyy"); + +
+ + @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { + @activity.Title + } + else + { + @activity.Title + } + +
+
+
+ Type: + + + @ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) + + + + @displayText: + + + + @dateTimeText + +
+
+ + + @statusText + + +
+
+ @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { +
+
+
+ } + + + @if (ViewActivityHelper.CanCertificateawarded(activity)) + { + +
+ + + + + + + + + + + + + + + Certificate: + awarded [@dateTimeText] + +
+ + } + else + { +
+ @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { +
@activity.TotalActivities of @activity.CompletedActivities activities completed
+ } + else + { +
+ No certificate available +
+ } + @if (activity.CertificateEnabled == true) + { +
+ Includes a certificate + + + + + + + + + + + + + + + +
+ } +
+ } +
+
+
+
+
+ } +
+ +
+ @await Html.PartialAsync("_ActivityTablePaging", Model) +
Not seeing what you are looking for? Go to learning history
+
+
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/RecentMyLearningActivitiesPaging.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/RecentMyLearningActivitiesPaging.cshtml new file mode 100644 index 000000000..4abefe945 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/RecentMyLearningActivitiesPaging.cshtml @@ -0,0 +1,30 @@ + diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/_ActivityTablePaging.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/_ActivityTablePaging.cshtml index 2a67789be..b66b04168 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/_ActivityTablePaging.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/_ActivityTablePaging.cshtml @@ -1,7 +1,7 @@ @using System.Web; @using LearningHub.Nhs.WebUI.Models.Learning @using LearningHub.Nhs.WebUI.Models.Search; -@model MyLearningViewModel; +@model MyLearningUserActivitiesViewModel; @{ diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index 96c3d46c2..9e99cd4b9 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -130,11 +130,11 @@ "ClientId": "LearningHubOAClient" } }, - "MoodleAPIConfig": { - "BaseUrl": "", - "MoodleWSRestFormat": "json", - "WSToken": "" - }, + "MoodleAPIConfig": { + "BaseUrl": "https://moodle-test.test-learninghub.org.uk/webservice/rest/server.php", + "MoodleWSRestFormat": "json", + "WSToken": "b19bb181cf81d6d2db7e72f683e18c10" + }, "AssetDetails": { "FilePath1": "145b58b5-2f3c-4c56-850e-ea5bd6dd232b", "FileName1": "Learning Hub Resource structure for search.docx", diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index 727560e3f..85d3ea00e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj index cbab74f45..059d93749 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj @@ -17,6 +17,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IResourceActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IResourceActivityRepository.cs index 0c778605c..07568624d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IResourceActivityRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IResourceActivityRepository.cs @@ -79,6 +79,22 @@ int CreateActivity( /// ResourceActivity. Task> GetByUserIdFromSP(int userId, Nhs.Models.MyLearning.MyLearningRequestModel requestModel, DateTimeOffset detailedMediaActivityRecordingStartDate); + /// + /// Get recent my learning Activity By user id. + /// + /// The user id. + /// requestModel. + /// ResourceActivity. + Task > GetUserRecentMyLearningActivities(int userId, Nhs.Models.MyLearning.MyLearningRequestModel requestModel); + + /// + /// Get user learning history. + /// + /// The user id. + /// requestModel. + /// ResourceActivity. + Task> GetUserLearningHistory(int userId, Nhs.Models.MyLearning.MyLearningRequestModel requestModel); + /// /// Check if scorm activity has been completed. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs index d055990ce..44e678633 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs @@ -414,6 +414,11 @@ public LearningHubDbContextOptions Options ///
public virtual DbSet NodeContentAdminViewModel { get; set; } + /// + /// Gets or sets the User recent my learning activities. + /// + public virtual DbSet MyLearningActivitiesViewModel { get; set; } + /// /// Gets or sets the node resource.. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj index 009e83ef8..476ecb503 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -24,6 +24,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ResourceActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ResourceActivityRepository.cs index 12710bfe8..d27b6f13c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ResourceActivityRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ResourceActivityRepository.cs @@ -11,6 +11,7 @@ namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Hierarchy; using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; using LearningHub.Nhs.OpenApi.Repositories.Helpers; @@ -180,7 +181,7 @@ public async Task IsScormActivityFinished(int userId, int scormActivityId) /// requestModel. /// detailedMediaActivityRecordingStartDate. /// ResourceActivity. - public async Task> GetByUserIdFromSP(int userId,Nhs.Models.MyLearning.MyLearningRequestModel requestModel, DateTimeOffset detailedMediaActivityRecordingStartDate) + public async Task> GetByUserIdFromSP(int userId, Nhs.Models.MyLearning.MyLearningRequestModel requestModel, DateTimeOffset detailedMediaActivityRecordingStartDate) { (DateTimeOffset? startDate, DateTimeOffset? endDate) = this.ApplyDatesFilter(requestModel); (string strResourceTypes, bool resourceTypeFlag) = this.ApplyResourceTypesfilters(requestModel); @@ -232,6 +233,46 @@ public async Task> GetByUserIdFromSP(int userId,Nhs return listOfresourceActivities.OrderByDescending(r => r.ActivityStart).AsQueryable(); } + /// + /// Get User Recent My LearningActivities. + /// + /// The user id. + /// requestModel. + /// + public async Task> GetUserRecentMyLearningActivities(int userId, Nhs.Models.MyLearning.MyLearningRequestModel requestModel) + { + try + { + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + var result = await DbContext.MyLearningActivitiesViewModel.FromSqlRaw("EXEC activity.GetUserRecentLearningActivities @userId", param0).AsNoTracking().ToListAsync(); + return result; + } + catch (Exception ex) + { + return null; + } + } + + /// + /// Get User Recent My LearningActivities. + /// + /// The user id. + /// requestModel. + /// + public async Task> GetUserLearningHistory(int userId, Nhs.Models.MyLearning.MyLearningRequestModel requestModel) + { + try + { + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + var result = await DbContext.MyLearningActivitiesViewModel.FromSqlRaw("EXEC activity.GetUsersLearningHistory @userId", param0).AsNoTracking().ToListAsync(); + return result; + } + catch (Exception ex) + { + return null; + } + } + /// /// Get Resource Activity by user id. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 6fe3b2dfe..5abc71951 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -17,7 +17,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs index ee573d43b..1721140b1 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs @@ -24,6 +24,14 @@ public interface IMoodleApiService /// List of MoodleCourseResponseModel. Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber); + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// pageNumber. + /// List of MoodleCourseResponseModel. + Task> GetRecentEnrolledCoursesAsync(int userId, int? months = null); + /// /// GetEnrolledCoursesAsync. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs index b72447a00..3b53d59cc 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs @@ -19,6 +19,22 @@ public interface IMyLearningService /// The . Task GetActivityDetailed(int userId, MyLearningRequestModel requestModel); + /// + /// Gets the user recent my leraning activities. + /// + /// /// The user id. + /// The request model. + /// The . + Task GetUserRecentMyLearningActivitiesAsync(int userId, MyLearningRequestModel requestModel); + + /// + /// Gets history of users my leraning activities. + /// + /// /// The user id. + /// The request model. + /// The . + Task GetUserLearningHistoryAsync(int userId, MyLearningRequestModel requestModel); + /// /// Gets the played segment data for the progress modal in My Learning screen. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index bf7b23c23..a8953e951 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -30,7 +30,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs index 08786db8d..80c61d1d8 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs @@ -101,6 +101,75 @@ public async Task> GetEnrolledCoursesAsync(int u } + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// The page Number. + /// A representing the result of the asynchronous operation. + public async Task> GetRecentEnrolledCoursesAsync(int userId, int? months = null) + { + try + { + userId = 3; + var parameters = new Dictionary + { + { "userid", userId.ToString() }, + { "months", months.ToString() } + }; + + // Fetch enrolled courses + var recentEnrolledCourses = await GetCallMoodleApiAsync>( + "mylearningservice_get_recent_courses", + parameters + ); + + if (recentEnrolledCourses == null || recentEnrolledCourses.Count == 0) + return new List(); + + return recentEnrolledCourses.ToList(); + } + catch(Exception ex) + { + return null; + } + } + + /// + /// GetUserLearningHistory. + /// + /// Moodle user id. + /// The page Number. + /// A representing the result of the asynchronous operation. + public async Task> GetUserLearningHistoryAsync(int userId, int? months = null) + { + try + { + userId = 3; + var parameters = new Dictionary + { + { "userid", userId.ToString() }, + { "months", months.ToString() } + }; + + // Fetch enrolled courses + var recentEnrolledCourses = await GetCallMoodleApiAsync>( + "mylearningservice_get_recent_courses", + parameters + ); + + if (recentEnrolledCourses == null || recentEnrolledCourses.Count == 0) + return new List(); + + return recentEnrolledCourses.ToList(); + } + catch (Exception ex) + { + return null; + } + } + /// /// GetEnrolledCoursesAsync. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs index a6d2f1b06..fdb019e2a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs @@ -2,6 +2,8 @@ { using System; using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.PerformanceData; using System.Linq; using System.Threading.Tasks; using AutoMapper; @@ -25,6 +27,10 @@ public class MyLearningService : IMyLearningService /// The resourceActivityRepository. ///
private readonly IResourceActivityRepository resourceActivityRepository; + + /// + /// The catalogueNodeVersionRepository. + /// private readonly ICatalogueNodeVersionRepository catalogueNodeVersionRepository; /// @@ -52,6 +58,11 @@ public class MyLearningService : IMyLearningService /// private readonly IMediaResourceActivityRepository mediaResourceActivity; + /// + /// The moodleApiService. + /// + private readonly IMoodleApiService moodleApiService; + /// /// The mapper. /// @@ -74,6 +85,7 @@ public class MyLearningService : IMyLearningService /// The settings. /// The scormActivityRepository. /// The mediaResourceActivity. + /// The moodleApiService. public MyLearningService( IResourceActivityRepository resourceActivityRepository, IMediaResourcePlayedSegmentRepository mediaResourcePlayedSegmentRepository, @@ -83,7 +95,8 @@ public MyLearningService( IMapper mapper, IOptions settings, IScormActivityRepository scormActivityRepository, - IMediaResourceActivityRepository mediaResourceActivity) + IMediaResourceActivityRepository mediaResourceActivity, + IMoodleApiService moodleApiService) { this.resourceActivityRepository = resourceActivityRepository; this.mediaResourcePlayedSegmentRepository = mediaResourcePlayedSegmentRepository; @@ -94,6 +107,7 @@ public MyLearningService( this.settings = settings.Value; this.scormActivityRepository = scormActivityRepository; this.mediaResourceActivity = mediaResourceActivity; + this.moodleApiService = moodleApiService; } /// @@ -120,6 +134,178 @@ public async Task GetActivityDetailed(int userId, M return viewModel; } + /// + /// Gets the user recent my leraning activities.. + /// + /// /// The user id. + /// The request model. + /// The . + public async Task GetUserRecentMyLearningActivitiesAsync(int userId, MyLearningRequestModel requestModel) + { + try + { + var result = await resourceActivityRepository.GetUserRecentMyLearningActivities(userId, requestModel); + + var entrolledCourses = await this.moodleApiService.GetRecentEnrolledCoursesAsync(userId, 6); + + var mappedMyLearningActivities = result.Select(Activity => new MyLearningCombainedActivitiesViewModel + { + UserId = userId, + ResourceId = Activity.ResourceId, + ResourceVersionId = Activity.ResourceVersionId, + ResourceReferenceId = Activity.ResourceReferenceId, + IsCurrentResourceVersion = Activity.IsCurrentResourceVersion, + MajorVersion = Activity.MajorVersion, + MinorVersion = Activity.MinorVersion, + ResourceType = Activity.ResourceType, + Title = Activity.Title, + CertificateEnabled = Activity.CertificateEnabled, + ActivityStatus = Activity.ActivityStatus, + ActivityDate = Activity.ActivityDate, + ScorePercentage = Activity.ScorePercentage, + TotalActivities = 0, + CompletedActivities = 0, + }).ToList(); + + var mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombainedActivitiesViewModel + { + UserId = userId, + ResourceId = (int)course.Id, + ResourceVersionId = (int)course.Id, + IsCurrentResourceVersion = true, + ResourceReferenceId = (int)course.Id, + MajorVersion = 1, + MinorVersion = 0, + ResourceType = ResourceTypeEnum.Moodle, + Title = course.DisplayName, + CertificateEnabled = course.CertificateEnabled, + ActivityStatus = (bool)course.Completed ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, + ActivityDate = DateTimeOffset.FromUnixTimeMilliseconds((long)course.LastAccess), + ScorePercentage = Convert.ToInt32(course.ProgressPercentage.TrimEnd('%')), + TotalActivities = course.TotalActivities, + CompletedActivities = course.CompletedActivities, + }).ToList(); + + // Combine both result sets + var combainedUserActivities = mappedMyLearningActivities.Concat(mappedEnrolledCourses).ToList(); + + if (requestModel.Complete) + { + combainedUserActivities = combainedUserActivities.Where(x => x.ActivityStatus == ActivityStatusEnum.Completed || x.ActivityStatus == ActivityStatusEnum.Passed).ToList(); + } + else if (requestModel.Incomplete) + { + combainedUserActivities = combainedUserActivities.Where(x => x.ActivityStatus == ActivityStatusEnum.Incomplete || x.ActivityStatus == ActivityStatusEnum.Failed).ToList(); + } + + var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(requestModel.Skip).Take(requestModel.Take).ToList(); + + // Count total records. + MyLearningActivitiesDetailedViewModel viewModel = new MyLearningActivitiesDetailedViewModel() + { + TotalCount = combainedUserActivities.Count(), + Activities = pagedResults, + }; + + return viewModel; + } + catch (Exception ex) + { + return null; + } + } + + /// + /// Gets the user learning history activities. + /// + /// /// The user id. + /// The request model. + /// The . + public async Task GetUserLearningHistoryAsync(int userId, MyLearningRequestModel requestModel) + { + try + { + var result = await resourceActivityRepository.GetUserLearningHistory(userId, requestModel); + + var entrolledCourses = await this.moodleApiService.GetRecentEnrolledCoursesAsync(userId); + + List mappedMyLearningActivities = new(); + List mappedEnrolledCourses = new(); + List combainedUserActivities = new(); + + if (result != null) + { + mappedMyLearningActivities = result.Select(activity => new MyLearningCombainedActivitiesViewModel + { + UserId = userId, + ResourceId = activity.ResourceId, + ResourceVersionId = activity.ResourceVersionId, + ResourceReferenceId = activity.ResourceReferenceId, + IsCurrentResourceVersion = activity.IsCurrentResourceVersion, + MajorVersion = activity.MajorVersion, + MinorVersion = activity.MinorVersion, + ResourceType = activity.ResourceType, + Title = activity.Title, + CertificateEnabled = activity.CertificateEnabled, + ActivityStatus = activity.ActivityStatus, + ActivityDate = activity.ActivityDate, + ScorePercentage = activity.ScorePercentage, + TotalActivities = 0, + CompletedActivities = 0, + }).ToList(); + } + + if (entrolledCourses != null) + { + mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombainedActivitiesViewModel + { + UserId = userId, + ResourceId = (int)course.Id, + ResourceVersionId = (int)course.Id, + IsCurrentResourceVersion = true, + ResourceReferenceId = (int)course.Id, + MajorVersion = 1, + MinorVersion = 0, + ResourceType = ResourceTypeEnum.Moodle, + Title = course.DisplayName, + CertificateEnabled = course.CertificateEnabled, + ActivityStatus = (bool)course.Completed ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, + ActivityDate = DateTimeOffset.FromUnixTimeMilliseconds((long)course.LastAccess), + ScorePercentage = int.TryParse(course.ProgressPercentage.TrimEnd('%'), out var score) ? score : 0, + TotalActivities = course.TotalActivities, + CompletedActivities = course.CompletedActivities, + }).ToList(); + } + + // Combine both result sets + combainedUserActivities = mappedMyLearningActivities.Concat(mappedEnrolledCourses).ToList(); + + if (requestModel.Complete) + { + combainedUserActivities = combainedUserActivities.Where(x => x.ActivityStatus == ActivityStatusEnum.Completed || x.ActivityStatus == ActivityStatusEnum.Passed).ToList(); + } + else if (requestModel.Incomplete) + { + combainedUserActivities = combainedUserActivities.Where(x => x.ActivityStatus == ActivityStatusEnum.Incomplete || x.ActivityStatus == ActivityStatusEnum.Failed).ToList(); + } + + var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(requestModel.Skip).Take(requestModel.Take).ToList(); + + // Count total records. + MyLearningActivitiesDetailedViewModel viewModel = new MyLearningActivitiesDetailedViewModel() + { + TotalCount = combainedUserActivities.Count(), + Activities = pagedResults, + }; + + return viewModel; + } + catch (Exception ex) + { + return null; + } + } + /// /// Gets the played segment data for the progress modal in My Learning screen. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserService.cs index 70153fd24..45ae92f7a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserService.cs @@ -1,5 +1,6 @@ namespace LearningHub.Nhs.OpenApi.Services.Services { + using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -188,8 +189,15 @@ public async Task GetByUsernameAsync(string userName) /// The . public async Task GetByIdAsync(int id) { - var user = await userRepository.GetByIdAsync(id); - return mapper.Map(user); + try + { + var user = await userRepository.GetByIdAsync(id); + return mapper.Map(user); + } + catch(Exception ex) + { + return null; + } } /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index c16540ee6..65710bbfd 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs index 023faa590..e810e95d2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs @@ -52,6 +52,32 @@ public async Task GetActivityDetailed([FromBody] MyLearningReques return this.Ok(activityModel); } + /// + /// Gets the user recent my leraning activities. + /// + /// The request model. + /// The . + [HttpPost] + [Route("GetUserRecentMyLearningActivities")] + public async Task GetUserRecentMyLearningActivities([FromBody] MyLearningRequestModel requestModel) + { + var activityModel = await this.myLearningService.GetUserRecentMyLearningActivitiesAsync(this.CurrentUserId.GetValueOrDefault(), requestModel); + return this.Ok(activityModel); + } + + /// + /// Gets the user recent my leraning activities. + /// + /// The request model. + /// The . + [HttpPost] + [Route("GetUserLearningHistory")] + public async Task GetUserLearningHistory([FromBody] MyLearningRequestModel requestModel) + { + var activityModel = await this.myLearningService.GetUserLearningHistoryAsync(this.CurrentUserId.GetValueOrDefault(), requestModel); + return this.Ok(activityModel); + } + /// /// Gets the played segment data for the progress modal in My Learning screen. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index e8ddf4415..22aaae154 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -18,6 +18,7 @@ + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj index 227e30bf8..1ae229307 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj @@ -16,7 +16,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj index 3af90e39a..59ee42314 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj index c6d03d856..123cee758 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj @@ -19,7 +19,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj index 78f41769e..17b91b264 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj @@ -17,7 +17,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj index f87bb47d5..85cb95f53 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj @@ -20,7 +20,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 3b09a079f..eadeefebb 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -29,7 +29,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/web.config b/WebAPI/LearningHub.Nhs.API/web.config index 798b63b5d..a8d351424 100644 --- a/WebAPI/LearningHub.Nhs.API/web.config +++ b/WebAPI/LearningHub.Nhs.API/web.config @@ -2,6 +2,9 @@ + + + diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index 1963fd724..a6436881d 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index e7a054e23..684a874e2 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index 7e09ec673..3aff1ade8 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 6da0f9200..af23aff67 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index 024756991..1a35f760a 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index 6b6743743..a7eb5fce3 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index 44f8119cb..aa727f01b 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index 73e18184e..5accd4eaf 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -25,7 +25,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index 7b945c483..2044a7dd0 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index 189b0348e..d73a8c0d3 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -10,7 +10,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index 090dce6fc..9a15c5ddc 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index a9b7683f7..989c47f2d 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index 71195c130..6e5a38b22 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From b5741b25736385d6ab600cdf7babb90dabb342c6 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 11 Aug 2025 14:38:44 +0100 Subject: [PATCH 017/124] Removed the secret from appsettings --- LearningHub.Nhs.WebUI/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index 9e99cd4b9..66d4a0515 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -133,7 +133,7 @@ "MoodleAPIConfig": { "BaseUrl": "https://moodle-test.test-learninghub.org.uk/webservice/rest/server.php", "MoodleWSRestFormat": "json", - "WSToken": "b19bb181cf81d6d2db7e72f683e18c10" + "WSToken": "" }, "AssetDetails": { "FilePath1": "145b58b5-2f3c-4c56-850e-ea5bd6dd232b", From 63d6771505c623f32625c68ed3b5562195760919 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 11 Aug 2025 14:42:20 +0100 Subject: [PATCH 018/124] Reverted the changes --- .../Styles/nhsuk/layout.scss | 634 ------------------ 1 file changed, 634 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss index 4448f4ee9..915f4721f 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss @@ -5,141 +5,11 @@ body { overflow-wrap: break-word; } -.nhsuk-header { - padding: 0 px2rem(32); -} - -.nhsuk-hero { - background-color: $color_nhsuk-blue; - color: #fff; - position: relative; -} - -.nhsuk-hero a, -.nhsuk-hero a:visited, -.nhsuk-hero a:hover, -.nhsuk-hero a:active, -.nhsuk-hero i { - color: $color_nhsuk-white; -} - - -.nhsuk-width-container.app-width-container { - max-width: px2rem(1208); - margin: 0 auto; - padding-left: px2rem(32); - padding-right: px2rem(32); -} - -.nhsuk-header .nhsuk-width-container.app-width-container { - max-width: px2rem(1144); - margin: 0 auto; -} - -.nhsuk-width-container.app-width-container.beta-banner { - padding: px2rem(8) px2rem(32); - max-width: px2rem(1208); - margin: 0 auto; -} - -.nhsuk-header .nhsuk-header__container::after { - content: none; -} - -.nhsuk-header__navigation.app-width-container { - max-width: px2rem(1144); -} - -.app-width-container--full { - margin: 0; - max-width: none -} .app-main-wrapper--no-padding { padding: 0 } -.nhsuk-header__container.app-width-container { - display: flex; - justify-content: space-between; - gap: 0 px2rem(24); - padding: px2rem(16) 0; -} - -.nhsuk-header__content { - display: flex; - align-items: center; - min-height: px2rem(40); - margin-left: auto; -} - -.nhsuk-header__logo { - flex: 1 0 0; -} - -.nhsuk-header__logo .nhsuk-header__link--service { - display: inline-flex; -} - -.nhsuk-header__service-name { - font-size: px2rem(19); -} - -.nhsuk-account__login { - font-size: px2rem(14); - float: right; - position: relative; - z-index: 2; - display: flex; - justify-content: space-between; - align-items: center; - gap: px2rem(24); -} - -.nhsuk-header__notification-dot { - position: absolute; - top: px2rem(8); - right: px2rem(-10); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: white; - min-width: px2rem(18); - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - padding: px2rem(1) px2rem(3) 0; -} - -.nhsuk-header__menu { - display: none; -} - -.nhsuk-header__search .nhsuk-search__input { - width: px2rem(260); -} - -.nhsuk-header__search { - .nhsuk-search__input { - width: px2rem(260); - - &::-moz-placeholder { - opacity: 1; - } - } - - #search > label.nhsuk-u-visually-hidden { - background-color: $nhsuk-white; - } -} - -.nhsuk-account__login--link, -.nhsuk-account__login--link:visited, -.nhsuk-account__login--link:hover { - color: #fff; -} - .beta-banner { background-color: $color_nhsuk-grey-5; color: $nhsuk-text-color; @@ -170,42 +40,6 @@ body { font-size: px2rem(16); } -.nhsuk-footer { - padding: px2rem(48) 0; -} - - -#header-dropdown-menu-control { - opacity: 0; - position: absolute; -} - -#header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { - display: block -} - -#header-mobile-search-control { - display: none; -} - -.nhsuk-header__break { - display: none; -} - -.nhsuk-header__mobile-only-nav { - display: none; -} - -.nhsuk-header__mobile-break { - display: none; -} - -.nhsuk-header__navigation-item--current { - a { - font-weight: bold; - } -} - button[data-toggle="modal"] { color: #005eb8; padding: 0; @@ -270,489 +104,21 @@ li.autosuggestion-option:last-of-type { border-bottom: none !important; } - -/* side navigation styles */ -.side-nav__list { - list-style: none; - margin: 0; - padding: 0; - border-right: 1px solid #d8dde0; -} - -.side-nav__item { - border-bottom: 1px solid #d8dde0; - margin-bottom: 0 !important; - padding: 0; -} - - - - -.side-nav__item:last-child { - border-bottom: none; -} - -.side-nav__item a { - font-weight: 700; - display: block; - padding: 22px 12px; -} - - - -.side-nav__item--active a { - text-decoration: none; - color: $nhsuk-black; -} - -.side-nav__item--active { - background-color: #e8f0f7; -} - - - -.side-nav__toggle, -.side-nav__close { - display: none; - background: #e8f0f7; - color: #005eb8; - border: none; - font-size: 16px; - padding: 10px 16px; - border-radius: 25px; - margin: 10px; - cursor: pointer; -} - -.menu-icon { - display: inline-flex; - justify-content: center; - align-items: center; - width: 20px; - height: 20px; - box-sizing: border-box; - padding: 0; - border: 1px solid #d8dde0; - border-radius: 50%; - background-color: white; - transition: background-color 0.3s ease; -} - - -.side-nav__container { - position: fixed; - top: 0; - left: 0; - width: 80%; - max-width: 320px; - height: 100%; - background-color: #e8f0f7; - border-right: 40px solid #005eb8; - box-sizing: border-box; - transform: translateX(-100%); - transition: transform 0.3s ease-in-out; - z-index: 1000; - box-shadow: 4px 0 10px rgba(0, 0, 0, 0.1); - display: flex; - flex-direction: column; - overflow: visible; -} - - -.side-nav__close { - position: relative; - right: -35px; - z-index: 2; - align-self: flex-end; - text-align: right; -} - - -.nav-toggle:checked + label.side-nav__toggle + .side-nav__container { - transform: translateX(0); -} - - -.nav-toggle:checked + label .menu-icon { - transform: rotate(180deg); -} - - - -/* large desktop */ -@media (min-width: px2rem(990)) { - - .nhsuk-header__navigation-item--current a { - border-bottom: 4px solid $nhsuk-grey-lighter; - font-weight: normal; - } - - .nhsuk-header__navigation-link { - position: relative; - } - - .nhsuk-header__navigation-item:last-child { - margin-right: 16px; - } -} - -@media (min-width: px2rem(768)) { - .nav-toggle, - .side-nav__toggle, - .side-nav__close { - display: none !important; - } - - .side-nav__container { - position: static; - transform: none !important; - height: auto; - box-shadow: none; - display: block; - width: auto; - background-color: #ffffff; - border-right: none; - } -} - - /* small desktop */ @media (max-width: px2rem(989)) { - - .nhsuk-header__container { - flex-wrap: wrap; - } - - .nhsuk-header { - padding: 0; - } - - .nhsuk-header__container.app-width-container { - flex-wrap: wrap; - gap: 0 0; - padding: px2rem(16) px2rem(32); - } - - .nhsuk-header__link--service { - align-items: center; - -ms-flex-align: center; - margin-bottom: 0; - width: auto; - } - - .nhsuk-header__service-name { - padding-left: px2rem(16); - } - - .nhsuk-header__logo { - order: 0; - } - - .nhsuk-account__login { - order: 1; - margin-left: auto; - margin-right: 0px; - } - - .nhsuk-header__break { - display: block; - width: 100%; - height: px2rem(24); - order: 2 - } - - .nhsuk-header__search { - order: 3; - flex-grow: 1; - margin-left: 0; - margin-right: px2rem(24); - } - - .nhsuk-header__menu { - display: block; - position: relative; - order: 4; - flex: 0 0 px2rem(74); - } - - .nhsuk-header__navigation-list .nhsuk-header__navigation-item, - .nhsuk-header__navigation .nhsuk-header__navigation-title { - border-top: 1px solid $color_nhsuk-grey-4; - } - - .nhsuk-header__menu-toggle { - text-align: center; - margin: 0; - right: 0; - font-weight: 600; - } - - .nhsuk-header__search-form { - display: flex; - } - - .nhsuk-header__search .nhsuk-search__input { - flex: 1 0 0; - } - - #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { - display: block; - } - - .nhsuk-header__notification-dot { - position: absolute; - top: px2rem(15); - left: px2rem(115); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: $nhsuk-white; - min-width: px2rem(18); - width: fit-content; - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - } - - .nhsuk-header__navigation-item--current .nhsuk-header__notification-dot { - left: px2rem(125); - } - - .nhsuk-header__menu-notification-dot { - position: absolute; - top: px2rem(-5); - right: px2rem(-6); - background: $nhsuk-error-color; - width: px2rem(12); - height: px2rem(12); - border-radius: px2rem(6); - box-shadow: 0 0 0 2px white; - z-index: 10; - } - .autosuggestion-menu { top: 100%; } } - /* tablet */ @media (max-width: px2rem(768)) { - - .nhsuk-width-container.app-width-container, - .nhsuk-width-container.app-width-container.beta-banner { - padding-left: px2rem(16); - padding-right: px2rem(16); - } - - .nhsuk-back-link { - padding: 0.5rem 0; - } - - .nhsuk-header__menu .nhsuk-header__not-mobile { - display: none; - } - .autosuggestion-menu { top: 100%; } - - .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__mobile-only-nav { - display: flex; - order: 1; - justify-content: space-around; - gap: 0 px2rem(16); - align-items: flex-start; - flex-wrap: wrap; - width: px2rem(166); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__menu { - margin-right: px2rem(12); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { - margin-left: px2rem(12); - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-break { - display: block; - width: 100%; - height: 0; - } - - .nhsuk-header__link--service { - flex-direction: column; - align-items: flex-start; - } - - .nhsuk-header__notification-dot { - top: px2rem(13); - left: px2rem(100); - } - - .nhsuk-header__service-name { - padding: px2rem(12) 0 0; - } - - .nhsuk-header__search-toggle { - position: relative; - height: px2rem(40); - order: 2; - padding: px2rem(7) px2rem(10) 0; - margin: 0 - } - - .nhsuk-header__search .nhsuk-search__submit { - padding-top: nhsuk-spacing(1); - } - - .nhsuk-header__menu { - order: 3; - } - - .nhsuk-header__search { - order: 4; - width: 100%; - flex-grow: 1; - margin: px2rem(16) px2rem(-16) 0; - border-bottom: 1px solid $color_nhsuk-grey-4; - } - - #header-mobile-search-control { - display: block; - opacity: 0; - position: absolute; - } - - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } - - - .side-nav__toggle, .side-nav__close { - display: inline-block; - } - - .side-nav__close { - background-color: transparent; - } - - .nav-toggle:checked + label.side-nav__toggle .menu-icon, - .nav-toggle:checked + label.side-nav__toggle + .side-nav__container .side-nav__close .menu-icon { - background-color: #e8f0f7; - } } /* mobile */ @media (max-width: px2rem(640)) { - - .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__logo { - max-width: none; - } - - .nhsuk-header__mobile-only-nav { - display: flex; - order: 1; - justify-content: space-around; - gap: 0 px2rem(16); - align-items: flex-start; - flex-wrap: wrap; - width: px2rem(166); - } - - .nhsuk-header__pre-login .nhsuk-header__mobile-only-nav { - align-items: center; - justify-content: flex-end; - padding-bottom: px2rem(16); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__menu { - margin-right: px2rem(12); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { - margin-left: px2rem(12); - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-break { - display: block; - width: 100%; - height: 0; - } - - .nhsuk-header__link--service { - flex-direction: column; - align-items: flex-start; - } - - .nhsuk-header__notification-dot { - top: px2rem(13); - left: px2rem(100); - } - - .nhsuk-header__service-name { - padding: px2rem(12) 0 0; - } - - .nhsuk-header__search-toggle { - position: relative; - height: px2rem(40); - order: 2; - padding: px2rem(7) px2rem(10) 0; - margin: 0 - } - - .nhsuk-header__search .nhsuk-search__submit { - padding-top: nhsuk-spacing(1); - } - - .nhsuk-header__menu { - order: 3; - } - - .nhsuk-header__search { - order: 4; - width: 100%; - flex-grow: 1; - margin: px2rem(16) px2rem(-16) 0; - border-bottom: 1px solid $color_nhsuk-grey-4; - } - - #header-mobile-search-control { - display: block; - opacity: 0; - position: absolute; - } - - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } - .autosuggestion-menu { top: 100%; } From 7a9a6161ef48e2f1ead7720117f095b03501a7da Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 11 Aug 2025 15:11:24 +0100 Subject: [PATCH 019/124] DB changes included and corrected the typo in the class --- .../Helpers/ViewActivityHelper.cs | 4 +- .../MyLearningUserActivitiesViewModel.cs | 6 +- .../Services/MyLearningService.cs | 14 ++-- .../LearningHub.Nhs.Database.sqlproj | 2 + .../GetUserRecentLearningActivities.sql | 82 +++++++++++++++++++ .../Activity/GetUsersLearningHistory.sql | 50 +++++++++++ 6 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserRecentLearningActivities.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUsersLearningHistory.sql diff --git a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs index 3deeb1b56..6a83b80f2 100644 --- a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs @@ -128,7 +128,7 @@ public static string GetResourceTypeDesc(ResourceTypeEnum resourceType) /// /// The activity. /// The string. - public static string GetActivityStatusDisplayText(MyLearningCombainedActivitiesViewModel activity) + public static string GetActivityStatusDisplayText(MyLearningCombinedActivitiesViewModel activity) { if (activity.ActivityStatus == ActivityStatusEnum.Completed || activity.ActivityStatus == ActivityStatusEnum.Passed || activity.ActivityStatus == ActivityStatusEnum.Passed || activity.ActivityStatus == ActivityStatusEnum.Viewed || activity.ActivityStatus == ActivityStatusEnum.Downloaded) { @@ -145,7 +145,7 @@ public static string GetActivityStatusDisplayText(MyLearningCombainedActivitiesV ///
/// The activityDetailedItemViewModel. /// The bool. - public static bool CanCertificateawarded(this MyLearningCombainedActivitiesViewModel activitiesViewModel) + public static bool CanCertificateawarded(this MyLearningCombinedActivitiesViewModel activitiesViewModel) { if (activitiesViewModel.CertificateEnabled == true) { diff --git a/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs b/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs index 7b8ec1a39..8aa35c88b 100644 --- a/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs @@ -23,7 +23,7 @@ public class MyLearningUserActivitiesViewModel : MyLearningRequestModel ///
public MyLearningUserActivitiesViewModel() { - this.Activities = new List(); + this.Activities = new List(); } /// @@ -32,7 +32,7 @@ public MyLearningUserActivitiesViewModel() /// MyLearningRequestModel. public MyLearningUserActivitiesViewModel(MyLearningRequestModel requestModel) { - this.Activities = new List(); + this.Activities = new List(); foreach (PropertyInfo prop in requestModel.GetType().GetProperties()) { this.GetType().GetProperty(prop.Name).SetValue(this, prop.GetValue(requestModel, null), null); @@ -62,7 +62,7 @@ public MyLearningUserActivitiesViewModel(MyLearningRequestModel requestModel) /// /// Gets or sets the Activities. /// - public List Activities { get; set; } + public List Activities { get; set; } /// /// Gets or sets the learning result paging. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs index fdb019e2a..9f44364a1 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs @@ -148,7 +148,7 @@ public async Task GetUserRecentMyLearning var entrolledCourses = await this.moodleApiService.GetRecentEnrolledCoursesAsync(userId, 6); - var mappedMyLearningActivities = result.Select(Activity => new MyLearningCombainedActivitiesViewModel + var mappedMyLearningActivities = result.Select(Activity => new MyLearningCombinedActivitiesViewModel { UserId = userId, ResourceId = Activity.ResourceId, @@ -167,7 +167,7 @@ public async Task GetUserRecentMyLearning CompletedActivities = 0, }).ToList(); - var mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombainedActivitiesViewModel + var mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombinedActivitiesViewModel { UserId = userId, ResourceId = (int)course.Id, @@ -229,13 +229,13 @@ public async Task GetUserLearningHistoryA var entrolledCourses = await this.moodleApiService.GetRecentEnrolledCoursesAsync(userId); - List mappedMyLearningActivities = new(); - List mappedEnrolledCourses = new(); - List combainedUserActivities = new(); + List mappedMyLearningActivities = new(); + List mappedEnrolledCourses = new(); + List combainedUserActivities = new(); if (result != null) { - mappedMyLearningActivities = result.Select(activity => new MyLearningCombainedActivitiesViewModel + mappedMyLearningActivities = result.Select(activity => new MyLearningCombinedActivitiesViewModel { UserId = userId, ResourceId = activity.ResourceId, @@ -257,7 +257,7 @@ public async Task GetUserLearningHistoryA if (entrolledCourses != null) { - mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombainedActivitiesViewModel + mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombinedActivitiesViewModel { UserId = userId, ResourceId = (int)course.Id, diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index 59f6120a2..821629fb9 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -539,6 +539,8 @@ + + diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserRecentLearningActivities.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserRecentLearningActivities.sql new file mode 100644 index 000000000..ca8bf962c --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserRecentLearningActivities.sql @@ -0,0 +1,82 @@ +------------------------------------------------------------------------------- +-- Author Swapnamol Abraham +-- Created 29-07-2025 +-- Purpose Get Users recent learning acrtivities +-- +-- Modification History +------------------------------------------------------------------------------- +ALTER PROCEDURE [activity].[GetUserRecentLearningActivities] ( + @userId INT + ) +AS +BEGIN + + WITH CTERecentActivities AS ( + SELECT + ra.Id AS ActivityId, + ara.LaunchResourceActivityId AS LaunchResourceActivityId, + ra.UserId AS UserId, + ra.ResourceId AS ResourceId, + r.CurrentResourceVersionId AS ResourceVersionId, + CASE WHEN r.CurrentResourceVersionId = ra.ResourceVersionId THEN 1 ELSE 0 END AS IsCurrentResourceVersion, + ( + SELECT TOP 1 rr.OriginalResourceReferenceId + FROM [resources].[ResourceReference] rr + WHERE rr.ResourceId = rv.ResourceId AND rr.Deleted = 0 + ) AS ResourceReferenceID, + ra.MajorVersion AS MajorVersion, + ra.MinorVersion AS MinorVersion, + ra.NodePathId AS NodePathId, + r.ResourceTypeId AS ResourceType, + rv.Title AS Title, + rv.CertificateEnabled AS CertificateEnabled, + rvp.ProviderId AS ProviderId, + ISNULL(ara.ActivityStatusId, ra.ActivityStatusId) AS ActivityStatus, + ra.ActivityStart AS ActivityDate, + ISNULL(ara.DurationSeconds, 0) AS ActivityDurationSeconds, + ara.Score AS ScorePercentage, + arv.AssessmentType AS AssessmentType, + arv.PassMark AS PassMark, + asra.score AS AssesmentScore, + mar.SecondsPlayed AS SecondsPlayed, + mar.PercentComplete AS PercentComplete, + sa.CmiCoreLesson_status AS CmiCoreLessonstatus, + sa.CmiCoreScoreMax AS CmiCoreScoreMax, + sa.CmiCoreSession_time AS CmiCoreSessiontime, + sa.DurationSeconds AS DurationSeconds, + ROW_NUMBER() OVER (PARTITION BY ra.ResourceId ORDER BY ISNULL(ara.ActivityEnd, ra.ActivityStart) DESC) AS rn + FROM activity.ResourceActivity ra + LEFT JOIN activity.ResourceActivity ara ON ara.LaunchResourceActivityId = ra.Id + INNER JOIN [resources].[Resource] r ON ra.ResourceId = r.Id + INNER JOIN [resources].[ResourceVersion] rv ON rv.Id = ra.ResourceVersionId AND rv.Deleted = 0 + LEFT JOIN [resources].[ResourceVersionProvider] rvp on rv.Id = rvp.ResourceVersionId + LEFT JOIN [resources].[AssessmentResourceVersion] arv ON arv.ResourceVersionId = ra.ResourceVersionId + LEFT JOIN [activity].[AssessmentResourceActivity] asra ON asra.ResourceActivityId = ra.Id + LEFT JOIN [activity].[MediaResourceActivity] mar ON mar.ResourceActivityId = ra.Id + LEFT JOIN [activity].[ScormActivity] sa ON sa.ResourceActivityId = ra.Id + WHERE ra.LaunchResourceActivityId IS NULL AND ra.userid = @userId + AND ra.deleted = 0 + AND r.ResourceTypeId IN(2,6,7,10,11) AND ra.ActivityStart >= DATEADD(MONTH, -6, SYSDATETIMEOFFSET()) +) +SELECT ActivityId, + LaunchResourceActivityId, + UserId, + ResourceId, + ResourceVersionId, + CAST(IsCurrentResourceVersion AS BIT) AS IsCurrentResourceVersion, + ResourceReferenceId, + MajorVersion, + MinorVersion, + NodePathId, + ResourceType, + Title, + CertificateEnabled, + ProviderId, + ActivityStatus, + ActivityDate, + ActivityDurationSeconds, + ScorePercentage +FROM CTERecentActivities +WHERE rn = 1 order by ActivityDate desc; + +END \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUsersLearningHistory.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUsersLearningHistory.sql new file mode 100644 index 000000000..bf7218e30 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUsersLearningHistory.sql @@ -0,0 +1,50 @@ +------------------------------------------------------------------------------- +-- Author Swapnamol Abraham +-- Created 08-08-2025 +-- Purpose Get Users learning history +-- +-- Modification History +------------------------------------------------------------------------------- +ALTER PROCEDURE [activity].[GetUsersLearningHistory] ( + @userId INT + ) +AS +BEGIN + + SELECT + ra.Id AS ActivityId, + ara.LaunchResourceActivityId AS LaunchResourceActivityId, + ra.UserId AS UserId, + ra.ResourceId AS ResourceId, + r.CurrentResourceVersionId AS ResourceVersionId, + CAST(CASE WHEN r.CurrentResourceVersionId = ra.ResourceVersionId THEN 1 ELSE 0 END AS BIT) AS IsCurrentResourceVersion, + ( + SELECT TOP 1 rr.OriginalResourceReferenceId + FROM [resources].[ResourceReference] rr + WHERE rr.ResourceId = rv.ResourceId AND rr.Deleted = 0 + ) AS ResourceReferenceId, + ra.MajorVersion AS MajorVersion, + ra.MinorVersion AS MinorVersion, + ra.NodePathId AS NodePathId, + r.ResourceTypeId AS ResourceType, + rv.Title AS Title, + rv.CertificateEnabled AS CertificateEnabled, + rvp.ProviderId AS ProviderId, + ISNULL(ara.ActivityStatusId, ra.ActivityStatusId) AS ActivityStatus, + ra.ActivityStart AS ActivityDate, + ISNULL(ara.DurationSeconds, 0) AS ActivityDurationSeconds, + ara.Score AS ScorePercentage +FROM activity.ResourceActivity ra +LEFT JOIN activity.ResourceActivity ara + ON ara.LaunchResourceActivityId = ra.Id +INNER JOIN [resources].[Resource] r ON ra.ResourceId = r.Id +INNER JOIN [resources].[ResourceVersion] rv ON rv.Id = ra.ResourceVersionId AND rv.deleted =0 +LEFT JOIN [resources].[ResourceVersionProvider] rvp on rv.Id = rvp.ResourceVersionId +LEFT JOIN [resources].[AssessmentResourceVersion] arv ON arv.ResourceVersionId = ra.ResourceVersionId +LEFT JOIN [activity].[AssessmentResourceActivity] asra ON asra.ResourceActivityId = ra.Id +LEFT JOIN [activity].[MediaResourceActivity] mar ON mar.ResourceActivityId = ra.Id +LEFT JOIN [activity].[ScormActivity] sa ON sa.ResourceActivityId = ra.Id +WHERE ra.LaunchResourceActivityId IS NULL and ra.userid = @userId AND ra.deleted = 0 +ORDER BY ra.Id asc + +END \ No newline at end of file From cd70949280c510cfb57684e4455f0af036623946 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Tue, 12 Aug 2025 12:07:22 +0100 Subject: [PATCH 020/124] Corrected the build errors --- .../Services/IMoodleApiService.cs | 10 +++++- .../Services/MoodleApiService.cs | 32 +++++++++++++++++++ .../Services/MyLearningService.cs | 2 +- .../GetUserRecentLearningActivities.sql | 2 +- .../Activity/GetUsersLearningHistory.sql | 2 +- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs index 1721140b1..398f5dd36 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs @@ -30,7 +30,15 @@ public interface IMoodleApiService /// Moodle user id. /// pageNumber. /// List of MoodleCourseResponseModel. - Task> GetRecentEnrolledCoursesAsync(int userId, int? months = null); + Task> GetRecentEnrolledCoursesAsync(int userId, int? months = null); + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// pageNumber. + /// List of MoodleCourseResponseModel. + Task> GetEnrolledCoursesHistoryAsync(int userId); /// /// GetEnrolledCoursesAsync. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs index 80c61d1d8..8ce99d996 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs @@ -136,6 +136,38 @@ public async Task> GetRecentEnrolledCour } } + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle user id. + /// A representing the result of the asynchronous operation. + public async Task> GetEnrolledCoursesHistoryAsync(int userId) + { + try + { + userId = 3; + var parameters = new Dictionary + { + { "userid", userId.ToString() } + }; + + // Fetch enrolled courses + var recentEnrolledCourses = await GetCallMoodleApiAsync>( + "mylearningservice_get_recent_courses", + parameters + ); + + if (recentEnrolledCourses == null || recentEnrolledCourses.Count == 0) + return new List(); + + return recentEnrolledCourses.ToList(); + } + catch (Exception ex) + { + return null; + } + } + /// /// GetUserLearningHistory. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs index 9f44364a1..a9c10a37c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs @@ -227,7 +227,7 @@ public async Task GetUserLearningHistoryA { var result = await resourceActivityRepository.GetUserLearningHistory(userId, requestModel); - var entrolledCourses = await this.moodleApiService.GetRecentEnrolledCoursesAsync(userId); + var entrolledCourses = await this.moodleApiService.GetEnrolledCoursesHistoryAsync(userId); List mappedMyLearningActivities = new(); List mappedEnrolledCourses = new(); diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserRecentLearningActivities.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserRecentLearningActivities.sql index ca8bf962c..98e1e0353 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserRecentLearningActivities.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserRecentLearningActivities.sql @@ -5,7 +5,7 @@ -- -- Modification History ------------------------------------------------------------------------------- -ALTER PROCEDURE [activity].[GetUserRecentLearningActivities] ( +CREATE PROCEDURE [activity].[GetUserRecentLearningActivities] ( @userId INT ) AS diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUsersLearningHistory.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUsersLearningHistory.sql index bf7218e30..ac0cf5431 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUsersLearningHistory.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUsersLearningHistory.sql @@ -5,7 +5,7 @@ -- -- Modification History ------------------------------------------------------------------------------- -ALTER PROCEDURE [activity].[GetUsersLearningHistory] ( +CREATE PROCEDURE [activity].[GetUsersLearningHistory] ( @userId INT ) AS From 7a3409b6e519419eff5d6d76d3f56822fbe5b485 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Tue, 12 Aug 2025 12:22:20 +0100 Subject: [PATCH 021/124] TD-5761: Latest changes. --- .../Controllers/MyAccountController.cs | 67 +++++- .../Helpers/SelectListHelper.cs | 24 +++ .../Interfaces/ICountryService.cs | 12 ++ .../Interfaces/IUserService.cs | 14 ++ .../UserProfile/MyAccountLocationViewModel.cs | 66 ++++++ .../Services/CountryService.cs | 56 +++++ LearningHub.Nhs.WebUI/Services/UserService.cs | 68 ++++++ .../Views/MyAccount/ChangeLocation.cshtml | 194 ------------------ .../Views/MyAccount/Index.cshtml | 180 ++++++++-------- .../MyAccount/MyAccountChangeLocation.cshtml | 113 ++++++++++ .../Views/MyAccount/MyAccountSecurity.cshtml | 145 +++++++------ .../Views/MyAccount/MyEmployment.cshtml | 180 ++++++++-------- .../Views/Notification/Index.cshtml | 35 +++- 13 files changed, 710 insertions(+), 444 deletions(-) create mode 100644 LearningHub.Nhs.WebUI/Helpers/SelectListHelper.cs create mode 100644 LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountLocationViewModel.cs delete mode 100644 LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLocation.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountChangeLocation.cshtml diff --git a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs index 61c119f2a..428d2b133 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Drawing; using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -20,11 +21,14 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using Newtonsoft.Json.Linq; using NHSUKViewComponents.Web.ViewModels; + using static IdentityModel.ClaimComparer; using ChangePasswordViewModel = LearningHub.Nhs.WebUI.Models.UserProfile.ChangePasswordViewModel; using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration; @@ -197,25 +201,66 @@ public async Task UpdatePersonalDetails(MyAccountPersonalDetailsV /// /// ChangeLocation. /// - /// country id. /// ActionResult. [HttpGet] [Route("myaccount/ChangeLocation")] - public async Task ChangeLocation(int? selectedCountryId) + public async Task ChangeLocation() { this.TempData.Clear(); - var userLocationViewModel = await this.userService.GetUserLocationDetailsAsync(); - var userProfileSummary = await this.userService.GetUserProfileSummaryAsync(); - if (selectedCountryId.HasValue) + var userLocationViewModel = await this.userService.GetMyAccountLocationDetailsAsync(); + var allUKcountries = await this.countryService.GetAllUKCountries(); + var allUKnoncountries = await this.countryService.GetAllNonUKCountries(); + var regions = await this.regionService.GetAllAsync(); + + List radio = new List(); + foreach (var country in allUKcountries) { - userLocationViewModel.SelectedCountryId = selectedCountryId; + var newradio = new RadiosItemViewModel(country.Id.ToString(), country.Name, false, country.Name); + radio.Add(newradio); } - await this.multiPageFormService.SetMultiPageFormData( - userLocationViewModel, - MultiPageFormDataFeature.AddRegistrationPrompt, - this.TempData); - return this.View("ChangeLocation", new Tuple(userProfileSummary, userLocationViewModel)); + if (allUKnoncountries.Any(v => v.Id == userLocationViewModel.SelectedCountryId)) + { + ////userLocationViewModel.SelectedOtherCountry = true; + userLocationViewModel.SelectedOtherCountryId = userLocationViewModel.SelectedCountryId; + userLocationViewModel.SelectedCountryId = 0; + } + + userLocationViewModel.Country = radio; + userLocationViewModel.OtherCountryOptions = SelectListHelper.MapOptionsToSelectListItems(allUKnoncountries, userLocationViewModel.SelectedOtherCountryId); + + userLocationViewModel.RegionOptions = SelectListHelper.MapOptionsToSelectListItems(regions, userLocationViewModel.SelectedRegionId); + return this.View("MyAccountChangeLocation", userLocationViewModel); + } + + /// + /// To update location details. + /// + /// model. + /// The . + [HttpPost] + public async Task UpdateMyAccountLocationDetails(MyAccountLocationViewModel model) + { + if (this.ModelState.IsValid) + { + if (model?.SelectedCountryId == 0) + { + model.SelectedCountryId = model.SelectedOtherCountryId; + } + + if (model.SelectedCountryId != 1) + { + model.SelectedRegionId = null; + } + + await this.userService.UpdateMyAccountLocationDetailsAsync(this.CurrentUserId, model); + this.ViewBag.SuccessMessage = CommonValidationErrorMessages.CountrySuccessMessage; + return this.View("SuccessMessage"); + } + else + { + return this.View("MyAccountChangeLocation", model); + } } /// diff --git a/LearningHub.Nhs.WebUI/Helpers/SelectListHelper.cs b/LearningHub.Nhs.WebUI/Helpers/SelectListHelper.cs new file mode 100644 index 000000000..089665bfe --- /dev/null +++ b/LearningHub.Nhs.WebUI/Helpers/SelectListHelper.cs @@ -0,0 +1,24 @@ +namespace LearningHub.Nhs.WebUI.Helpers +{ + using System.Collections.Generic; + using System.Linq; + using elfhHub.Nhs.Models.Common; + using Microsoft.AspNetCore.Mvc.Rendering; + + /// + /// SelectListHelper. + /// + public static class SelectListHelper + { + /// + /// MapOptionsToSelectListItems. + /// + /// options. + /// selectedId. + /// SelectListItem. + public static IEnumerable MapOptionsToSelectListItems(IEnumerable options, int? selectedId = null) + { + return options.Select(o => new SelectListItem(o.Name, o.Id.ToString(), o.Id == selectedId)).ToList(); + } + } +} diff --git a/LearningHub.Nhs.WebUI/Interfaces/ICountryService.cs b/LearningHub.Nhs.WebUI/Interfaces/ICountryService.cs index 8b5fe7542..916159fe7 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/ICountryService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/ICountryService.cs @@ -29,5 +29,17 @@ public interface ICountryService /// /// A representing the result of the asynchronous operation. Task> GetAllAsync(); + + /// + /// The GetAllUKCountries. + /// + /// A representing the result of the asynchronous operation. + Task> GetAllUKCountries(); + + /// + /// The GetAllNonUKCountries. + /// + /// A representing the result of the asynchronous operation. + Task> GetAllNonUKCountries(); } } diff --git a/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs b/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs index 320dcb9c3..4fda81d60 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs @@ -504,5 +504,19 @@ public interface IUserService /// /// The . Task GetMyAccountSecurityDetailsAsync(); + + /// + /// Get MyAccount Location Details Async. + /// + /// The . + Task GetMyAccountLocationDetailsAsync(); + + /// + /// Update MyAccount Location Details Async. + /// + /// userId. + /// MyAccountLocationViewModel. + /// The . + Task UpdateMyAccountLocationDetailsAsync(int userId, MyAccountLocationViewModel model); } } diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountLocationViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountLocationViewModel.cs new file mode 100644 index 000000000..1e9a97b61 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountLocationViewModel.cs @@ -0,0 +1,66 @@ +namespace LearningHub.Nhs.WebUI.Models.UserProfile +{ + using System.Collections.Generic; + using System.ComponentModel; + using System.ComponentModel.DataAnnotations; + using elfhHub.Nhs.Models.Common; + using Microsoft.AspNetCore.Mvc.Rendering; + using NHSUKViewComponents.Web.ViewModels; + + /// + /// Defines the . + /// + public class MyAccountLocationViewModel + { + /// + /// Gets or sets the country id. + /// + [Required(ErrorMessage = "Select a country.")] + [DisplayName("Country")] + public int? SelectedCountryId { get; set; } + + /// + /// Gets or sets the region id. + /// + [Required(ErrorMessage = "Select a region.")] + [DisplayName("Region")] + public int? SelectedRegionId { get; set; } + + /// + /// Gets or sets selected country name. + /// + public string SelectedCountryName { get; set; } + + /// + /// Gets or sets selected region name. + /// + public string SelectedRegionName { get; set; } + + /// + /// Gets or sets the country id. + /// + ////[Required(ErrorMessage = "Select a country.")] + ////[DisplayName("Country")] + public int? SelectedOtherCountryId { get; set; } + + /// + /// Gets or sets a value indicating whether SelectedOtherCountry. + /// + public bool SelectedOtherCountry { get; set; } + + /// + /// Gets or sets the Country. + /// + public List Country { get; set; } + + /// + /// Gets or sets the OtherCountryOptions. + /// + public IEnumerable OtherCountryOptions { get; set; } + + /// + /// Gets or sets the RegionOptions. + /// + public IEnumerable RegionOptions { get; set; } + } +} diff --git a/LearningHub.Nhs.WebUI/Services/CountryService.cs b/LearningHub.Nhs.WebUI/Services/CountryService.cs index e1afd4b35..a09ca9eeb 100644 --- a/LearningHub.Nhs.WebUI/Services/CountryService.cs +++ b/LearningHub.Nhs.WebUI/Services/CountryService.cs @@ -118,5 +118,61 @@ public async Task> GetAllAsync() return viewmodel; } + + /// + /// Get a list of Uk Country records.. + /// + /// The . + public async Task> GetAllUKCountries() + { + List viewmodel = null; + + var client = await this.userApiHttpClient.GetClientAsync(); + + var request = $"Country/GetAllUKCountries"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject>(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } + + /// + /// Get a list of non Uk Country records. + /// + /// The . + public async Task> GetAllNonUKCountries() + { + List viewmodel = null; + + var client = await this.userApiHttpClient.GetClientAsync(); + + var request = $"Country/GetAllNonUKCountries"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject>(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } } } diff --git a/LearningHub.Nhs.WebUI/Services/UserService.cs b/LearningHub.Nhs.WebUI/Services/UserService.cs index 6978f4d38..eb761fe9c 100644 --- a/LearningHub.Nhs.WebUI/Services/UserService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserService.cs @@ -2029,6 +2029,41 @@ public async Task GetMyEmploymentDetailsAsy return viewModel; } + /// + /// GetMyAccountLocationDetailsAsync. + /// + /// The . + public async Task GetMyAccountLocationDetailsAsync() + { + MyAccountLocationViewModel viewModel = null; + + var personalDetailsModel = await this.GetCurrentUserPersonalDetailsAsync(); + var employmentViewModel = await this.GetPrimaryUserEmploymentForUser(personalDetailsModel.UserId); + + if (personalDetailsModel != null) + { + viewModel = new MyAccountLocationViewModel + { + SelectedCountryId = personalDetailsModel.CountryId, + SelectedRegionId = personalDetailsModel.RegionId, + }; + + if (personalDetailsModel.CountryId.HasValue) + { + var country = await this.countryService.GetByIdAsync(personalDetailsModel.CountryId.Value); + viewModel.SelectedCountryName = country.Name; + } + + if (personalDetailsModel.RegionId.HasValue) + { + var region = await this.regionService.GetByIdAsync(personalDetailsModel.RegionId.Value); + viewModel.SelectedRegionName = region.Name; + } + } + + return viewModel; + } + /// /// Get MyAccount Security Details Async. /// @@ -2056,5 +2091,38 @@ public async Task GetMyAccountSecurityDetailsAsync() return viewModel; } + + /// + /// Update MyAccount Location Details Async. + /// + /// userId. + /// MyAccountLocationViewModel. + /// The . + public async Task UpdateMyAccountLocationDetailsAsync(int userId, MyAccountLocationViewModel model) + { + PersonalDetailsViewModel personalDetailsViewModel = new PersonalDetailsViewModel + { + UserId = userId, + CountryId = model.SelectedCountryId, + RegionId = model.SelectedRegionId, + }; + + var json = JsonConvert.SerializeObject(personalDetailsViewModel); + var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); + + var client = await this.userApiHttpClient.GetClientAsync(); + var request = $"ElfhUser/UpdateLocationDetails"; + var response = await client.PutAsync(request, stringContent).ConfigureAwait(false); + + if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + if (!response.IsSuccessStatusCode) + { + throw new Exception("Update user location details failed!"); + } + } } } diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLocation.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLocation.cshtml deleted file mode 100644 index 431f23fce..000000000 --- a/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLocation.cshtml +++ /dev/null @@ -1,194 +0,0 @@ -@using LearningHub.Nhs.WebUI.Models.UserProfile -@model Tuple -@{ - ViewData["DisableValidation"] = true; - ViewData["Title"] = "My Account - Change country"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - const string professionalRegConditionalId = "professional-reg-conditional-Id"; - var optionYesSelected = true; -} -
-
- -
-
-
- Change country -
- @if (Model.Item2.FilterText == null) - { -

Update your country

- } - @if (Model.Item2.FilterText == null) - { -
-
-

- -

-
- @Model.Item1.CountryName -
-
-
-
-
-
- } - else - { -

Search results for @Model.Item2.FilterText

- } - -
-
- @if (Model.Item2.FilterText == null || Model.Item2.Country.Count() == 0) - { -

Search for example, England

- } -
- - - -
-
-
-
- @if (Model.Item2.Country != null && Model.Item2.Country.Count() > 0) - { -
-
-
-
- -

- Select your country -

-
-
- @foreach (var countries in Model.Item2.Country) - { -
- - - - -
- } -
- -
-
-
-
-
- -
- } - else - { - if (Model.Item2.FilterText != null || @errorHasOccurred) - { - @await Html.PartialAsync("SerachNoResults", Model.Item2.FilterText) - } - } -
- -
- -
- -

- How would you prefer to be contacted? -

-
-
- Select one option -
-
-
- - -
-
-
- -
- -
-
- - -
-
-
- -
- -
-
- - -
-
-
- -
- -
-
-
-
- - -
- - -
-
- -
-
-
-
-
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml index f365ed8b4..add30bab8 100644 --- a/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml @@ -1,8 +1,8 @@ @model LearningHub.Nhs.WebUI.Models.UserProfile.MyAccountPersonalDetailsViewModel -@{ - ViewData["Title"] = "Personal Details"; - var isgeneralUser = User.IsInRole("BasicUser"); +@{ + ViewData["Title"] = "Personal Details"; + var isgeneralUser = User.IsInRole("BasicUser"); } @section NavBreadcrumbs { @@ -26,110 +26,110 @@
-
- @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) -
-
+
+ @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) +
+

@ViewData["Title"]

@if (isgeneralUser) { -
- Information: -

If you have a work email address you can upgrade to a full user account. Full users are able to access learning community resources, contribute and share knowledge and content.

-
- } - - @if (this.ViewBag.CheckDetails == true) - { -
- Information: -

Please check that your details are up-to-date.

-
- } +
+ Information: +

If you have a work email address you can upgrade to a full user account. Full users are able to access learning community resources, contribute and share knowledge and content.

+
+ } - @if (!ViewData.ModelState.IsValid) - { - - } + @if (this.ViewBag.CheckDetails == true) + { +
+ Information: +

Please check that your details are up-to-date.

+
+ } - -
+ @if (!ViewData.ModelState.IsValid) + { + + } -
-
- Username -
-
- @Model.UserName -
-
-
-
- Name -
-
- @Model.Name -
-
-
-
- Preferred name -
-
- @Model.PreferredName -
+
-
-
-
- Primary email address -
- @if (Model.NewPrimaryEmailAddress != "" && Model.NewPrimaryEmailAddress != null) - { -
- Email pending +
+
+ Username +
+
+ @Model.UserName
- } - else - { +
+
+
+ Name +
- @Model.PrimaryEmailAddress + @Model.Name
- } -
+
+
+
+ Preferred name +
+
+ @Model.PreferredName +
- +
+
+
+ Primary email address +
+ @if (Model.NewPrimaryEmailAddress != "" && Model.NewPrimaryEmailAddress != null) + { +
+ Email pending +
+ } + else + { +
+ @Model.PrimaryEmailAddress +
+ } - @if (Model.NewPrimaryEmailAddress != "" && Model.NewPrimaryEmailAddress != null) - { -
-
-

- The email address @Model.NewPrimaryEmailAddress is pending validation. -
You can resend the confirmation email or cancel this email change. -

-
- } - - Update details - + - @if (this.ViewBag.CheckDetails == true) - { -
- -
- + @if (Model.NewPrimaryEmailAddress != "" && Model.NewPrimaryEmailAddress != null) + { +
+
+

+ The email address @Model.NewPrimaryEmailAddress is pending validation. +
You can resend the confirmation email or cancel this email change. +

+
- - } -
-
+ } + + + Update details + + + @if (this.ViewBag.CheckDetails == true) + { +
+ +
+ +
+
+ } +
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountChangeLocation.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountChangeLocation.cshtml new file mode 100644 index 000000000..67e728c82 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountChangeLocation.cshtml @@ -0,0 +1,113 @@ +@model LearningHub.Nhs.WebUI.Models.UserProfile.MyAccountLocationViewModel +@{ + ViewData["DisableValidation"] = true; + ViewData["Title"] = "My Account - Location"; + var errorHasOccurred = !ViewData.ModelState.IsValid; +} +
+
+ +
+
+
+ Update location details +
+ @* @if (Model.Item2.FilterText == null) + { +

Update your country

+ } *@ + @if (Model.SelectedCountryName != null) + { +
+
+

+ +

+
+ @Model.SelectedCountryName +
+
+
+
+
+
+ } + @* else + { +

Search results for @Model.Item2.FilterText

+ } *@ +
+
+ @if (Model.Country != null && Model.Country.Any()) + { + @foreach (var country in Model.Country) + { + @if (country.Label.ToUpper() == "ENGLAND") + { +
+
+ + +
+ + +
+
+
+ } + else + { +
+ + +
+ } + + } +
+
+ + +
+ + +
+
+
+ + } +
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountSecurity.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountSecurity.cshtml index 7c9ada9ba..cf7e2b431 100644 --- a/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountSecurity.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/MyAccountSecurity.cshtml @@ -1,7 +1,26 @@ @model LearningHub.Nhs.WebUI.Models.UserProfile.MyAccountSecurityViewModel -@{ - ViewData["Title"] = "Security"; +@{ + ViewData["Title"] = "Security"; +} +@section NavBreadcrumbs { + +
+
+
+
+ +
+ Home + +
+
+

My account

+
+
+
+
+
}
@@ -11,69 +30,69 @@
- @if (!ViewData.ModelState.IsValid) - { - - } + @if (!ViewData.ModelState.IsValid) + { + + } -

Security

-
+

Security

+
-
-
- Secondary email address -
-
- @Model.SecondaryEmailAddress -
+
+
+ Secondary email address +
+
+ @Model.SecondaryEmailAddress +
-
+
- - Change secondry email - + + Change secondry email + -
+ -
+
-
-
- Password -
-
- ************* -
+
+
+ Password +
+
+ ************* +
-
+
- - Change password - + + Change password + -
+ -
+
-
-
- Security questions -
-
- Set on @Model.SecurityQuestionLastUpdated -
+
+
+ Security questions +
+
+ Set on @Model.SecurityQuestionLastUpdated +
-
+
- - Change security question 2 - + + Change security questions + -
+ -
+
- @*
+ @*
First security question
@@ -107,20 +126,20 @@
*@ -
- - @if (this.ViewBag.CheckDetails == true) - { -
- -
- -
-
- } -
-
+ + + @if (this.ViewBag.CheckDetails == true) + { +
+ +
+ +
+
+ } +
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/MyEmployment.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/MyEmployment.cshtml index 7ec254a33..601ab4c3a 100644 --- a/LearningHub.Nhs.WebUI/Views/MyAccount/MyEmployment.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/MyEmployment.cshtml @@ -1,108 +1,126 @@ @model LearningHub.Nhs.WebUI.Models.UserProfile.MyAccountEmploymentDetailsViewModel -@{ - ViewData["Title"] = "My employment"; - var isgeneralUser = User.IsInRole("BasicUser"); +@{ + ViewData["Title"] = "My employment"; + var isgeneralUser = User.IsInRole("BasicUser"); } -
-
-
-
- @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) -
-
- @if (!ViewData.ModelState.IsValid) - { - - } -

@ViewData["Title"]

-

Job details

-
- -
-
Current role
-
@Model.JobRole
-
+@section NavBreadcrumbs { - @if (!string.IsNullOrEmpty(Model.MedicalCouncilNo)) - { -
-
Professional registration number
-
@Model.MedicalCouncilNo
+
+
+
+
+ +
+ Home + +
+
+

My account

+
- } - @if (!User.IsInRole("BasicUser")) +
+
+
+} +@*
*@ +
+
+
+ @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) +
+
+ @if (!ViewData.ModelState.IsValid) { -
-
Grade
-
@Model.Grade
-
+ + } +

@ViewData["Title"]

+

Job details

+
-
Primary specialty
-
@Model.PrimarySpecialty
+
Current role
+
@Model.JobRole
-
-
Start date
-
@Model.JobStartDate?.ToString("dd MMMM yyyy")
-
+ @if (!string.IsNullOrEmpty(Model.MedicalCouncilNo)) + { +
+
Professional registration number
+
@Model.MedicalCouncilNo
+
+ } + @if (!User.IsInRole("BasicUser")) + { +
+
Grade
+
@Model.Grade
+
-
-
Place of work
-
@Html.Raw(Model.PlaceOfWork)
-
- } -
+
+
Primary specialty
+
@Model.PrimarySpecialty
+
- - Update details - +
+
Start date
+
@Model.JobStartDate?.ToString("dd MMMM yyyy")
+
-

Location

+
+
Place of work
+
@Html.Raw(Model.PlaceOfWork)
+
+ } +
- -
+ + Update details + -
-
- Country -
-
- @Model.CountryName -
+

Location

+ + +
-
- @if (Model.CountryName == "England") - {
- Region + Country
- @Model.RegionName + @Model.CountryName
+ @if (Model.CountryName == "England") + { +
+
+ Region +
+
+ @Model.RegionName +
+ +
+ } +
+ + + Update location + + + @if (this.ViewBag.CheckDetails == true) + { +
+ +
+ +
+
} - - - - Update location - - - @if (this.ViewBag.CheckDetails == true) - { -
- -
- -
-
- } -
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Notification/Index.cshtml b/LearningHub.Nhs.WebUI/Views/Notification/Index.cshtml index 39f56672c..45331215f 100644 --- a/LearningHub.Nhs.WebUI/Views/Notification/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Notification/Index.cshtml @@ -1,11 +1,36 @@ @{ ViewData["Title"] = "System notifications"; } -
- @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) -
-
- +@section NavBreadcrumbs { + +
+
+
+
+ +
+ Home + +
+
+

My account

+
+
+
+
+
+} +
+
+
+ @await Component.InvokeAsync("SideNav", new { groupTitle = "Account" }) +
+
+
+ +
+
+
@section Scripts { From c747f51a5062d91cfb673ca1f5b79c157c242202 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Wed, 20 Aug 2025 14:31:22 +0100 Subject: [PATCH 022/124] TD-5711: My Learning page redesign --- .../Controllers/MyLearningController.cs | 47 + .../Controllers/MyRecentLearningController.cs | 495 ------- .../Interfaces/IMyLearningService.cs | 7 + .../MyLearningUserActivitiesViewModel.cs | 50 + .../Services/MyLearningService.cs | 18 +- .../MyLearning/DownloadActivityRecords.cshtml | 1222 +++++++++++++++++ .../Views/MyLearning/Index.cshtml | 106 +- .../Views/MyLearning/LearningHistory.cshtml | 199 ++- .../MyLearning/LearningHistoryPaging.cshtml | 76 + .../Shared/Components/SideNav/Default.cshtml | 4 +- .../Activity/IResourceActivityRepository.cs | 24 +- .../Activity/ResourceActivityRepository.cs | 71 +- .../Services/IMoodleApiService.cs | 10 +- .../Services/MoodleApiService.cs | 89 +- .../Services/MyLearningService.cs | 99 +- 15 files changed, 1828 insertions(+), 689 deletions(-) delete mode 100644 LearningHub.Nhs.WebUI/Controllers/MyRecentLearningController.cs create mode 100644 LearningHub.Nhs.WebUI/Views/MyLearning/DownloadActivityRecords.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistoryPaging.cshtml diff --git a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs index a1762ec71..0b3b49d4b 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs @@ -458,6 +458,9 @@ public async Task LearningHistory(MyLearningUserActivitiesViewMod Viewed = learningRequest.Viewed, Launched = learningRequest.Launched, CertificateEnabled = learningRequest.CertificateEnabled, + Catalogues = learningRequest.Catalogues, + Resources = learningRequest.Resources, + Courses = learningRequest.Courses, }; if (myLearningDashboard != null) @@ -627,6 +630,50 @@ public async Task ExportToPDF(MyLearningRequestModel myLearningRe return this.View(new Tuple(userDetails, response)); } + /// + /// Function to export activity report to pdf. + /// + /// myLearningRequestModel. + /// A representing the result of the asynchronous operation. + [Route("/MyLearning/DownloadActivities")] + [HttpPost] + public async Task DownloadActivities(MyLearningRequestModel myLearningRequestModel) + { + var filter = myLearningRequestModel; + filter.Skip = 0; + filter.Take = 999; + var userDetails = await this.userService.GetCurrentUserBasicDetailsAsync(); + var response = new MyLearningUserActivitiesViewModel(); + var result = await this.myLearningService.GetUserLearningHistory(filter); + if (result != null) + { + response.TotalCount = result.TotalCount; + response.Activities = result.Activities; + } + + Tuple modelData = Tuple.Create(userDetails, response); + var renderedViewHTML = RenderRazorViewToString(this, "DownloadActivityRecords", modelData); + ReportStatusModel reportStatusModel = new ReportStatusModel(); + var pdfReportResponse = await this.pdfReportService.PdfReport(renderedViewHTML, userDetails.Id); + if (pdfReportResponse != null) + { + do + { + reportStatusModel = await this.pdfReportService.PdfReportStatus(pdfReportResponse); + } + while (reportStatusModel.Id == 1); + + var pdfReportFile = await this.pdfReportService.GetPdfReportFile(pdfReportResponse); + if (pdfReportFile != null) + { + var fileName = "ActivityReport.pdf"; + return this.File(pdfReportFile, FileHelper.GetContentTypeFromFileName(fileName), fileName); + } + } + + return this.View(new Tuple(userDetails, response)); + } + /// /// Gets the played segment data for the progress modal in My Learning screen. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/MyRecentLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/MyRecentLearningController.cs deleted file mode 100644 index 2d909f839..000000000 --- a/LearningHub.Nhs.WebUI/Controllers/MyRecentLearningController.cs +++ /dev/null @@ -1,495 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Controllers -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net.Http; - using System.Threading.Tasks; - using LearningHub.Nhs.Models.MyLearning; - using LearningHub.Nhs.Models.Report; - using LearningHub.Nhs.WebUI.Configuration; - using LearningHub.Nhs.WebUI.Extensions; - using LearningHub.Nhs.WebUI.Filters; - using LearningHub.Nhs.WebUI.Helpers; - using LearningHub.Nhs.WebUI.Interfaces; - using LearningHub.Nhs.WebUI.Models; - using LearningHub.Nhs.WebUI.Models.Learning; - using Microsoft.AspNetCore.Authorization; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Mvc; - using Microsoft.AspNetCore.Mvc.Rendering; - using Microsoft.AspNetCore.Mvc.ViewEngines; - using Microsoft.AspNetCore.Mvc.ViewFeatures; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - - /// - /// The MyLearningController. - /// - [Authorize] - [ServiceFilter(typeof(LoginWizardFilter))] - public class MyRecentLearningController : BaseController - { - private const int MyLearningPageSize = 10; - private readonly IMyLearningService myLearningService; - private readonly IResourceService resourceService; - private readonly IHierarchyService hierarchyService; - private readonly IUserService userService; - private readonly IPDFReportService pdfReportService; - private readonly IFileService fileService; - private readonly string filePath; - - /// - /// Initializes a new instance of the class. - /// - /// The hostingEnvironment. - /// The logger. - /// The settings. - /// The httpClientFactory. - /// myLearning service. - /// resource Service. - /// hierarchy Service. - /// user Service. - /// PDF Report Service. - /// fileService. - public MyRecentLearningController(IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, IHttpClientFactory httpClientFactory, IMyLearningService myLearningService, IResourceService resourceService, IHierarchyService hierarchyService, IUserService userService, IPDFReportService pdfReportService, IFileService fileService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) - { - this.myLearningService = myLearningService; - this.resourceService = resourceService; - this.userService = userService; - this.pdfReportService = pdfReportService; - this.hierarchyService = hierarchyService; - this.fileService = fileService; - this.filePath = "CatalogueImageDirectory"; - } - - /// - /// RenderRazorViewToString. - /// - /// controller. - /// viewName. - /// model. - /// Html as string. - public static string RenderRazorViewToString(Controller controller, string viewName, object model = null) - { - controller.ViewData.Model = model; - using (var sw = new StringWriter()) - { - IViewEngine viewEngine = - controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as - ICompositeViewEngine; - ViewEngineResult viewResult = viewEngine.FindView(controller.ControllerContext, viewName, false); - - ViewContext viewContext = new ViewContext( - controller.ControllerContext, - viewResult.View, - controller.ViewData, - controller.TempData, - sw, - new HtmlHelperOptions()); - viewResult.View.RenderAsync(viewContext); - return sw.GetStringBuilder().ToString(); - } - } - - /// - /// Index. - /// - /// learningRequest. - /// The my learning dashboard type. - /// IActionResult. - [Route("MyRecentLearning")] - [Route("MyRecentLearning/activity")] - [HttpGet] - [HttpPost] - public async Task Index(MyLearningViewModel learningRequest = null, string myLearningDashboard = null) - { - var myLearningRequestModel = new MyLearningRequestModel - { - SearchText = learningRequest.SearchText?.Trim(), - Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, - Take = MyLearningPageSize, - TimePeriod = !string.IsNullOrWhiteSpace(learningRequest.TimePeriod) ? learningRequest.TimePeriod : "allDates", - StartDate = learningRequest.StartDate, - EndDate = learningRequest.EndDate, - Weblink = learningRequest.Weblink, - File = learningRequest.File, - Video = learningRequest.Video, - Article = learningRequest.Article, - Case = learningRequest.Case, - Image = learningRequest.Image, - Audio = learningRequest.Audio, - Elearning = learningRequest.Elearning, - Html = learningRequest.Html, - Assessment = learningRequest.Assessment, - Complete = learningRequest.Complete, - Incomplete = learningRequest.Incomplete, - Passed = learningRequest.Passed, - Failed = learningRequest.Failed, - Downloaded = learningRequest.Downloaded, - Viewed = learningRequest.Viewed, - Launched = learningRequest.Launched, - CertificateEnabled = learningRequest.CertificateEnabled, - }; - - if (myLearningDashboard != null) - { - if (myLearningDashboard == "my-in-progress") - { - myLearningRequestModel.Incomplete = true; - myLearningRequestModel.Failed = true; - } - else if (myLearningDashboard == "my-recent-completed") - { - myLearningRequestModel.Complete = true; - myLearningRequestModel.Passed = true; - myLearningRequestModel.Downloaded = true; - } - else if (myLearningDashboard == "my-certificates") - { - myLearningRequestModel.CertificateEnabled = true; - myLearningRequestModel.Complete = true; - myLearningRequestModel.Passed = true; - myLearningRequestModel.Downloaded = true; - } - } - - switch (learningRequest.MyLearningFormActionType) - { - case MyLearningFormActionTypeEnum.NextPageChange: - learningRequest.CurrentPageIndex += 1; - myLearningRequestModel.Skip = learningRequest.CurrentPageIndex * MyLearningPageSize; - break; - - case MyLearningFormActionTypeEnum.PreviousPageChange: - learningRequest.CurrentPageIndex -= 1; - myLearningRequestModel.Skip = learningRequest.CurrentPageIndex * MyLearningPageSize; - break; - case MyLearningFormActionTypeEnum.BasicSearch: - - myLearningRequestModel = new MyLearningRequestModel - { - SearchText = learningRequest.SearchText?.Trim(), - TimePeriod = !string.IsNullOrWhiteSpace(learningRequest.TimePeriod) ? learningRequest.TimePeriod : "allDates", - Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, - Take = MyLearningPageSize, - }; - break; - - case MyLearningFormActionTypeEnum.ApplyWeekFilter: - myLearningRequestModel = new MyLearningRequestModel - { - SearchText = learningRequest.SearchText?.Trim(), - TimePeriod = "thisWeek", - Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, - Take = MyLearningPageSize, - }; - break; - case MyLearningFormActionTypeEnum.ApplyMonthFilter: - myLearningRequestModel = new MyLearningRequestModel - { - SearchText = learningRequest.SearchText?.Trim(), - TimePeriod = "thisMonth", - Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, - Take = MyLearningPageSize, - }; - break; - - case MyLearningFormActionTypeEnum.ApplyTwelveMonthFilter: - myLearningRequestModel = new MyLearningRequestModel - { - SearchText = learningRequest.SearchText?.Trim(), - TimePeriod = "last12Months", - Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, - Take = MyLearningPageSize, - }; - break; - - case MyLearningFormActionTypeEnum.ApplyMajorFilters: - if (learningRequest.TimePeriod == "dateRange") - { - if (!this.ModelState.IsValid) - { - break; - } - - myLearningRequestModel.TimePeriod = learningRequest.TimePeriod; - myLearningRequestModel.StartDate = learningRequest.GetStartDate().HasValue ? learningRequest.GetStartDate().Value : null; - myLearningRequestModel.EndDate = learningRequest.GetEndDate().HasValue ? learningRequest.GetEndDate().Value : null; - } - - break; - - case MyLearningFormActionTypeEnum.ClearAllFilters: - - myLearningRequestModel = new MyLearningRequestModel - { - SearchText = learningRequest.SearchText?.Trim(), - Skip = learningRequest.CurrentPageIndex * MyLearningPageSize, - TimePeriod = "allDates", - Take = MyLearningPageSize, - }; - break; - } - - var result = await this.myLearningService.GetUserRecentMyLearningActivities(myLearningRequestModel); - var response = new MyLearningUserActivitiesViewModel(myLearningRequestModel); - - if (result != null) - { - response.TotalCount = result.TotalCount; - response.Activities = result.Activities; - } - - response.MyLearningPaging = new MyLearningPagingModel() { CurrentPage = learningRequest.CurrentPageIndex, PageSize = MyLearningPageSize, TotalItems = response.TotalCount, HasItems = response.TotalCount > 0 }; - this.ViewBag.MyLearningHelpUrl = this.Settings.SupportUrls.MyLearningHelpUrl; - return this.View(response); - } - - /// - /// Function to export activity report to pdf. - /// - /// myLearningRequestModel. - /// A representing the result of the asynchronous operation. - [Route("/MyLearning/ExportToPDF")] - [HttpPost] - public async Task ExportToPDF(MyLearningRequestModel myLearningRequestModel) - { - var filter = myLearningRequestModel; - filter.Skip = 0; - filter.Take = 999; - var userDetails = await this.userService.GetCurrentUserBasicDetailsAsync(); - var response = new MyLearningViewModel(); - var result = await this.myLearningService.GetActivityDetailed(filter); - if (result != null) - { - response.TotalCount = result.TotalCount; - response.Activities = result.Activities.Select(entry => new ActivityDetailedItemViewModel(entry)).ToList(); - if (response.Activities.Any()) - { - foreach (var activity in response.Activities) - { - if (!response.MostRecentResources.Contains(activity.ResourceId)) - { - activity.IsMostRecent = true; - response.MostRecentResources.Add(activity.ResourceId); - } - } - } - } - - Tuple modelData = Tuple.Create(userDetails, response); - var renderedViewHTML = RenderRazorViewToString(this, "ExportToPDF", modelData); - ReportStatusModel reportStatusModel = new ReportStatusModel(); - var pdfReportResponse = await this.pdfReportService.PdfReport(renderedViewHTML, userDetails.Id); - if (pdfReportResponse != null) - { - do - { - reportStatusModel = await this.pdfReportService.PdfReportStatus(pdfReportResponse); - } - while (reportStatusModel.Id == 1); - - var pdfReportFile = await this.pdfReportService.GetPdfReportFile(pdfReportResponse); - if (pdfReportFile != null) - { - var fileName = "ActivityReport.pdf"; - return this.File(pdfReportFile, FileHelper.GetContentTypeFromFileName(fileName), fileName); - } - } - - return this.View(new Tuple(userDetails, response)); - } - - /// - /// Gets the played segment data for the progress modal in My Learning screen. - /// - /// The resourceId. - /// The resourceVersionId. - /// The majorVersion. - /// The max time. - /// Return url. - /// The . - [HttpGet] - [Route("my-learning/activity/{resourceId}/view-progress")] - public async Task ViewProgress(int resourceId, int resourceReferenceId, int version, long maxTime, string returnUrl = "/") - { - this.ViewBag.ReturnUrl = returnUrl; - var playedSegments = await this.myLearningService.GetPlayedSegments(resourceId, version); - - var allSegments = new List(); - var currentTime = 0; - var mediaLengthInSeconds = maxTime / 1000M; - - foreach (var segment in playedSegments.OrderBy(p => p.SegmentStartTime)) - { - if (segment.SegmentStartTime > currentTime) - { - allSegments.Add(new ResourcePlayedSegment - { - SegmentStartTime = currentTime, - SegmentEndTime = segment.SegmentStartTime, - Played = false, - }); - } - - allSegments.Add(new ResourcePlayedSegment - { - SegmentStartTime = segment.SegmentStartTime, - SegmentEndTime = segment.SegmentEndTime, - Played = true, - }); - - currentTime = segment.SegmentEndTime; - } - - if (currentTime < mediaLengthInSeconds) - { - allSegments.Add(new ResourcePlayedSegment - { - SegmentStartTime = currentTime, - SegmentEndTime = (int)mediaLengthInSeconds, - Played = false, - }); - } - - allSegments.ForEach(s => s.Percentage = Math.Round((s.SegmentEndTime - s.SegmentStartTime) / mediaLengthInSeconds * 100, 2)); - - var vm = new ActivityViewProgress - { - ResourceReferenceId = resourceReferenceId, - Segments = allSegments, - MediaLength = ResourcePlayedSegment.GetDurationHhmmss((int)mediaLengthInSeconds), - }; - - return this.View(vm); - } - - /// - /// Gets the certificate details of an activity. - /// - /// The resourceReferenceId. - /// The majorVersion. - /// The minorVersion. - /// The user Id. - /// The downloadCert flag. - /// The . - [HttpGet] - [Route("mylearning/certificate/{resourceReferenceId}")] - [Route("mylearning/certificate/{resourceReferenceId}/{userId}")] - [Route("mylearning/certificate/{*path}")] - public async Task GetCertificateDetails(int resourceReferenceId, int? majorVersion = 0, int? minorVersion = 0, int? userId = 0, bool downloadCert = false) - { - CertificateDetails certificateDetails = null; - string base64Image = string.Empty; - var activity = await this.myLearningService.GetResourceCertificateDetails(resourceReferenceId, majorVersion, minorVersion, userId); - if (activity.Item1 > 0 && activity.Item2 != null && ViewActivityHelper.CanDownloadCertificate(new ActivityDetailedItemViewModel(activity.Item2))) - { - var resource = await this.resourceService.GetItemByIdAsync(resourceReferenceId); - var nodePathNodes = await this.hierarchyService.GetNodePathNodes(resource.NodePathId); - var currentUser = await this.userService.GetUserByUserIdAsync((userId == 0) ? this.CurrentUserId : (int)userId); - var userEmployment = await this.userService.GetUserEmploymentByIdAsync(currentUser.PrimaryUserEmploymentId ?? 0); - if (activity.Item2.CertificateUrl != null && downloadCert) - { - var file = await this.fileService.DownloadFileAsync(this.filePath, activity.Item2.CertificateUrl); - if (file != null) - { - byte[] imageArray = new BinaryReader(file.Content).ReadBytes((int)file.ContentLength); - base64Image = Convert.ToBase64String(imageArray); - } - } - - certificateDetails = new CertificateDetails { AccessCount = activity.Item1, ProfessionalRegistrationNumber = userEmployment?.MedicalCouncilNo, NodeViewModels = nodePathNodes, UserViewModel = currentUser, ResourceItemViewModel = resource, ActivityDetailedItemViewModel = new ActivityDetailedItemViewModel(activity.Item2), DownloadCertificate = downloadCert, CertificateBase64Image = base64Image }; - } - - return this.View("LearningCertificate", certificateDetails); - } - - /// - /// Gets the certificate details of an activity. - /// - /// The resourceReferenceId. - /// The majorVersion. - /// The minorVersion. - /// The userId. - /// The . - [HttpPost] - [Route("mylearning/downloadcertificate")] - public async Task DownloadCertificate(int resourceReferenceId, int? majorVersion = 0, int? minorVersion = 0, int? userId = 0) - { - CertificateDetails certificateDetails = null; - string base64Image = string.Empty; - var activity = await this.myLearningService.GetResourceCertificateDetails(resourceReferenceId, majorVersion, minorVersion, userId); - if (activity.Item1 > 0 && activity.Item2 != null && ViewActivityHelper.CanDownloadCertificate(new ActivityDetailedItemViewModel(activity.Item2))) - { - var resource = await this.resourceService.GetItemByIdAsync(resourceReferenceId); - var nodePathNodes = await this.hierarchyService.GetNodePathNodes(resource.NodePathId); - var currentUser = await this.userService.GetUserByUserIdAsync((userId == 0) ? this.CurrentUserId : (int)userId); - var userEmployment = await this.userService.GetUserEmploymentByIdAsync(currentUser.PrimaryUserEmploymentId ?? 0); - var resourceItemUrl = this.Settings.LearningHubWebUiUrl.Trim() + "Resource/" + resourceReferenceId + "/Item"; - if (activity.Item2.CertificateUrl != null) - { - var file = await this.fileService.DownloadFileAsync(this.filePath, activity.Item2.CertificateUrl); - if (file != null) - { - byte[] imageArray = new BinaryReader(file.Content).ReadBytes((int)file.ContentLength); - base64Image = Convert.ToBase64String(imageArray); - } - } - - certificateDetails = new CertificateDetails { AccessCount = activity.Item1, ProfessionalRegistrationNumber = userEmployment?.MedicalCouncilNo, NodeViewModels = nodePathNodes, UserViewModel = currentUser, ResourceItemViewModel = resource, ActivityDetailedItemViewModel = new ActivityDetailedItemViewModel(activity.Item2), DownloadCertificate = true, CertificateBase64Image = base64Image, PdfResoureItemUrl = resourceItemUrl }; - var renderedViewHTML = new List(); - certificateDetails.PageNo++; - renderedViewHTML.Add(RenderRazorViewToString(this, "LearningCertificate", certificateDetails)); - certificateDetails.PageNo++; - renderedViewHTML.Add(RenderRazorViewToString(this, "LearningCertificate", certificateDetails)); - - ReportStatusModel reportStatusModel = new ReportStatusModel(); - var pdfReportResponse = await this.pdfReportService.PdfReport(renderedViewHTML, currentUser.Id); - if (pdfReportResponse != null) - { - do - { - reportStatusModel = await this.pdfReportService.PdfReportStatus(pdfReportResponse); - } - while (reportStatusModel.Id == 1); - - var pdfReportFile = await this.pdfReportService.GetPdfReportFile(pdfReportResponse); - if (pdfReportFile != null) - { - string fileName = this.GenerateCertificateName(certificateDetails.ActivityDetailedItemViewModel.Title); - return this.File(pdfReportFile, FileHelper.GetContentTypeFromFileName(fileName), fileName); - } - } - } - - return this.View("LearningCertificate", certificateDetails); - } - - /// - /// Gets the Certificate name. - /// - /// The resourceTitile. - /// The . - private string GenerateCertificateName(string resourceTitile) - { - if (!string.IsNullOrEmpty(resourceTitile)) - { - if (resourceTitile.Length <= 71) - { - string filename = "LH_Certificate_" + resourceTitile + ".pdf"; - return filename; - } - else if (resourceTitile.Length > 71) - { - string filename = "LH_Certificate_" + resourceTitile.Truncate(67, true) + "_ " + ".pdf"; - return filename; - } - } - - return "LearningCertificate.pdf"; - } - } -} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs index 773b4791f..ad61e4efe 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs @@ -48,5 +48,12 @@ public interface IMyLearningService /// The userId. /// The . Task> GetResourceCertificateDetails(int resourceReferenceId, int? majorVersion = 0, int? minorVersion = 0, int? userId = 0); + + /// + /// Gets the resource URL for a given resource reference ID. + /// + /// resourceReferenceId. + /// The . + string GetResourceUrl(int resourceReferenceId); } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs b/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs index 8aa35c88b..0042021cb 100644 --- a/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/MyLearningUserActivitiesViewModel.cs @@ -68,5 +68,55 @@ public MyLearningUserActivitiesViewModel(MyLearningRequestModel requestModel) /// Gets or sets the learning result paging. /// public PagingViewModel MyLearningPaging { get; set; } + + /// + /// sets the list of certificate checkboxes. + /// + /// The . + public List CertificateFilterCheckbox() + { + var checkboxes = new List() + { + new CheckboxListItemViewModel("CertificateEnabled", "Certificate", null), + }; + return checkboxes; + } + + /// + /// sets the list of status checkboxes. + /// + /// The . + public List StatusFilterCheckbox() + { + var checkboxes = new List() + { + new CheckboxListItemViewModel("Complete", "Completed", null), + new CheckboxListItemViewModel("Incomplete", "In progress", null), + }; + return checkboxes; + } + + /// + /// sets the list of type checkboxes. + /// + /// The . + public List TypeFilterCheckbox() + { + var checkboxes = new List() + { + new CheckboxListItemViewModel("Article", "Article", null), + new CheckboxListItemViewModel("Assessment", "Assessment", null), + new CheckboxListItemViewModel("Audio", "Audio", null), + new CheckboxListItemViewModel("Case", "Case", null), + new CheckboxListItemViewModel("Elearning", "elearning", null), + new CheckboxListItemViewModel("File", "File", null), + new CheckboxListItemViewModel("Html", "HTML", null), + new CheckboxListItemViewModel("Image", "Image", null), + new CheckboxListItemViewModel("Video", "Video", null), + new CheckboxListItemViewModel("Weblink", "Weblink", null), + new CheckboxListItemViewModel("Courses", "Courses", null), + }; + return checkboxes; + } } } diff --git a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs index df38b15ef..0dfa182eb 100644 --- a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs +++ b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs @@ -7,8 +7,10 @@ using System.Threading.Tasks; using elfhHub.Nhs.Models.Common; using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; using Newtonsoft.Json; /// @@ -16,15 +18,19 @@ /// public class MyLearningService : BaseService, IMyLearningService { + private readonly Settings settings; + /// /// Initializes a new instance of the class. /// /// The learningHubHttpClient. /// The Open Api Http Client. /// The logger. - public MyLearningService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + /// The settings. + public MyLearningService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IOptions settings) : base(learningHubHttpClient, openApiHttpClient, logger) { + this.settings = settings.Value; } /// @@ -182,5 +188,15 @@ public async Task> GetResourceCertif return viewModel; } + + /// + /// GetCourseUrl. + /// + /// resourceReference Id. + /// return course URL. + public string GetResourceUrl(int resourceReferenceId) + { + return this.settings.LearningHubWebUiUrl.Trim() + "Resource/" + resourceReferenceId + "/Item"; + } } } diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/DownloadActivityRecords.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/DownloadActivityRecords.cshtml new file mode 100644 index 000000000..02bb94824 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/DownloadActivityRecords.cshtml @@ -0,0 +1,1222 @@ +@using LearningHub.Nhs.Models.Enums +@using LearningHub.Nhs.Models.Hierarchy +@using LearningHub.Nhs.Models.Resource.ResourceDisplay +@using LearningHub.Nhs.WebUI.Configuration; +@using LearningHub.Nhs.WebUI.Models +@using LearningHub.Nhs.WebUI.Models.Learning +@using LearningHub.Nhs.WebUI.Helpers; +@using Microsoft.Extensions.Options; +@inject IOptions settings +@model Tuple +@inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; +@inject LearningHub.Nhs.WebUI.Interfaces.IMyLearningService myLearningService; +@{ + ViewData["Title"] = "My learning activity"; + string spacer = string.Empty; + string GetMoodleCourseUrl(int courseId) + { + return moodleApiService.GetCourseUrl(courseId); + } + + string GetResourceUrl(int resourceReferenceId) + { + return myLearningService.GetResourceUrl(resourceReferenceId); + } +} + + + + + + @ViewData["Title"] + + + + + + +
+
+
+
+

Activity report: @Model.Item1.FirstName @Model.Item1.LastName (@Model.Item1.UserName)

+
+ + + +
+
+
+
+
+ @foreach (var activity in Model.Item2.Activities) + { + + var isCompleted = ViewActivityHelper.GetActivityStatusDisplayText(activity) == ActivityStatusEnum.Completed.ToString(); + + var typeLabelClass = isCompleted ? "nhsuk-u-primary-text-color" : "nhsuk-u-secondary-text-color"; + var accessedLabelClass = typeLabelClass; + var tagColorClass = isCompleted ? "nhsuk-tag--green" : "nhsuk-tag--blue"; + var statusText = isCompleted ? "Completed" : "In progress"; + var displayText = isCompleted ? "Completed" : "Accessed"; + + var activityDate = activity.ActivityDate.Date; + var today = DateTime.Today; + var dateTimeText = activityDate == today ? "Today" + : activityDate == today.AddDays(-1) ? "Yesterday" + : activityDate.ToString("dd MMM yyyy"); + +
+ + @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { + @activity.Title + } + else + { + @activity.Title + } + +
+
+
+ Type: + + + @ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) + + + + @displayText: + + + + @dateTimeText + +
+
+ + + @statusText + + +
+
+ @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { +
+
+
+ } + + + @if (ViewActivityHelper.CanCertificateawarded(activity)) + { + +
+ + + + + + + + + + + + + + + Certificate: + awarded [@dateTimeText] + +
+ + } + else + { +
+ @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + { +
@activity.TotalActivities of @activity.CompletedActivities activities completed
+ } + else + { +
+ No certificate available +
+ } + @if (activity.CertificateEnabled == true) + { +
+ Includes a certificate + + + + + + + + + + + + + + + + + +
+ } +
+ } +
+
+
+
+
+ } +
+
+
+
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml index 93df45e8e..3c44a8371 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml @@ -1,9 +1,11 @@ @using LearningHub.Nhs.Models.Enums @using LearningHub.Nhs.WebUI.Helpers @using LearningHub.Nhs.WebUI.Models +@using LearningHub.Nhs.WebUI.Models.Learning @inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; @model MyLearningUserActivitiesViewModel; @{ + ViewData["Title"] = "Recent learning"; var returnUrl = $"{Context.Request.Path}{Context.Request.QueryString}"; string GetMoodleCourseUrl(int courseId) { @@ -38,42 +40,60 @@
-
+
-
+
@await Component.InvokeAsync("SideNav", new { groupTitle = "Activity" })
-
-
-

Recent learning

+
+
+

Recent learning

-
- Show - - - All - - - - In progress - - - - Completed - +
+
+
+
+ No certificate available +
+ + Clear all filters + +
+
+ + + + Filter results + + + +
+
+ +
+ +
+ +
+ +
+
-
-
+
+ +
+
+
@@ -141,22 +161,22 @@ @if (ViewActivityHelper.CanCertificateawarded(activity)) { -
+
- - - - - - - + + + + + + + - - + + - Certificate: + Certificate: awarded [@dateTimeText]
@@ -164,10 +184,10 @@ } else { -
+
@if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") { -
@activity.TotalActivities of @activity.CompletedActivities activities completed
+
@activity.TotalActivities of @activity.CompletedActivities activities completed
} else { @@ -179,6 +199,7 @@ {
Includes a certificate + @@ -193,7 +214,8 @@ - + +
}
@@ -208,7 +230,7 @@
@await Html.PartialAsync("_ActivityTablePaging", Model) -
Not seeing what you are looking for? Go to learning history
+
Not seeing what you are looking for? Go to learning history
diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml index 93df45e8e..304c466ad 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml @@ -1,14 +1,18 @@ @using LearningHub.Nhs.Models.Enums @using LearningHub.Nhs.WebUI.Helpers @using LearningHub.Nhs.WebUI.Models +@using LearningHub.Nhs.WebUI.Models.Learning @inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; @model MyLearningUserActivitiesViewModel; @{ + ViewData["Title"] = "Learning history"; + var errorHasOccurred = !ViewData.ModelState.IsValid; var returnUrl = $"{Context.Request.Path}{Context.Request.QueryString}"; string GetMoodleCourseUrl(int courseId) { return moodleApiService.GetCourseUrl(courseId); } + var routeData = ViewActivityHelper.GetActivityParameters(Model); } @section styles { @@ -38,43 +42,123 @@
-
+
-
+
@await Component.InvokeAsync("SideNav", new { groupTitle = "Activity" })
-
-
-

Recent learning

+
+
+

Learning history

+ +
+ @if (errorHasOccurred) + { + + } +

+ You can use this page to search and filter learning resources you've accessed, download certificates and generate a report + of your activity. +

+ +
+ + + Learn how to manage My learning + + +
+

My learning displays an itemised activity table. This table details everything you have accessed in the Learning Hub.

+

You can search your learning activity by entering a search term in the search box. This will only search your learning activity and not the entire Learning Hub. Results will be displayed placing your search term as the filter. To return to your full Itemised activity, either select clear all filters or uncheck your search term(s).

+
+
+ +

+ Download a report of your learning. +

+ @if (Model.TotalCount != 0) + { +
+ +
+ } + else + { + + } + +
+ +

Search within My learning

+
+ + + + +
+

@Model.TotalCount activity result@(Model.TotalCount > 1 ? "s" : "")

+
+
+
-
- Show - - - All - - - - In progress - - - - Completed - +
+
+ +
+
+ Sorted by Date order +
+ + Clear all filters + +
+
+ + + Filter results + + + +
+
+ +
+ +
+ +
+
+ +
+ +
+
-
-
-
+
+ +
+
@foreach (var activity in Model.Activities) @@ -141,22 +225,22 @@ @if (ViewActivityHelper.CanCertificateawarded(activity)) { -
+
- - - - - - - + + + + + + + - - + + - Certificate: + Certificate: awarded [@dateTimeText]
@@ -164,7 +248,7 @@ } else { -
+
@if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") {
@activity.TotalActivities of @activity.CompletedActivities activities completed
@@ -179,21 +263,23 @@ {
Includes a certificate - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + +
}
@@ -207,8 +293,7 @@
- @await Html.PartialAsync("_ActivityTablePaging", Model) -
Not seeing what you are looking for? Go to learning history
+ @await Html.PartialAsync("LearningHistoryPaging", Model)
diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistoryPaging.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistoryPaging.cshtml new file mode 100644 index 000000000..0a4b5ccb0 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistoryPaging.cshtml @@ -0,0 +1,76 @@ +@using System.Web; +@using LearningHub.Nhs.WebUI.Models.Learning +@using LearningHub.Nhs.WebUI.Models.Search; +@model MyLearningUserActivitiesViewModel; + + +@{ + var pagingModel = Model.MyLearningPaging; + var showPaging = pagingModel.CurrentPage >= 0 && pagingModel.CurrentPage <= pagingModel.TotalPages - 1; + var previousMessage = $"{pagingModel.CurrentPage} of {pagingModel.TotalPages}"; + int CurrentPageNumber = pagingModel.CurrentPage + 1; + var nextMessage = string.Empty; + if (CurrentPageNumber <= pagingModel.TotalPages) + { + nextMessage = $"{CurrentPageNumber + 1} of {pagingModel.TotalPages}"; + } + else + { + previousMessage = $"{CurrentPageNumber - 1} of {pagingModel.TotalPages}"; + nextMessage = $"{CurrentPageNumber} of {pagingModel.TotalPages}"; + } + + var routeData = ViewActivityHelper.GetActivityParameters(Model); + routeData["CurrentPageIndex"] = pagingModel.CurrentPage.ToString(); + var nextRouteData = new Dictionary(routeData); + var previousRouteData = new Dictionary(routeData); + nextRouteData["MyLearningFormActionType"] = MyLearningFormActionTypeEnum.NextPageChange.ToString(); + previousRouteData["MyLearningFormActionType"] = MyLearningFormActionTypeEnum.PreviousPageChange.ToString(); +} + +@if (pagingModel.TotalPages > 1) +{ + +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Shared/Components/SideNav/Default.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/Components/SideNav/Default.cshtml index bef06e64d..9cd547e6a 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/Components/SideNav/Default.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/Components/SideNav/Default.cshtml @@ -3,8 +3,8 @@ @foreach (var group in Model.Groups) { - - + +
public const string EmailChangeValidationTokenInvalidMessage = "We cannot find the page you are looking for"; + + /// + /// Message if the validation token expired. + /// + public const string InvalidSecurityQuestionAnswer = "Enter an answer"; + + /// + /// Message if the validation token expired. + /// + public const string EmploymentDetailsUpdated = "Your employment details has been changed"; + + /// + /// security question Success Message. + /// + public const string SecurityQuestionSuccessMessage = "Your security questions has been changed"; + + /// + /// location Success Message. + /// + public const string LocationDetailsSuccessMessage = "Your location details has been changed"; + + /// + /// location Success Message. + /// + public const string PersonalDetailsSuccessMessage = "Your personal details has been changed"; } } diff --git a/LearningHub.Nhs.WebUI/Helpers/SelectListHelper.cs b/LearningHub.Nhs.WebUI/Helpers/SelectListHelper.cs index 089665bfe..1dcd3f53e 100644 --- a/LearningHub.Nhs.WebUI/Helpers/SelectListHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/SelectListHelper.cs @@ -20,5 +20,16 @@ public static IEnumerable MapOptionsToSelectListItems(IEnumerabl { return options.Select(o => new SelectListItem(o.Name, o.Id.ToString(), o.Id == selectedId)).ToList(); } + + /// + /// MapSelectListWithSelection. + /// + /// options. + /// selectedId. + /// SelectListItem. + public static IEnumerable MapSelectListWithSelection(IEnumerable options, string selectedId = null) + { + return options.Select(o => new SelectListItem(o.Text, o.Value, o.Value == selectedId)).ToList(); + } } } diff --git a/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs b/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs index 4889d7fb8..35bf84302 100644 --- a/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs +++ b/LearningHub.Nhs.WebUI/Models/SideMenu/SideNavigationConfiguration.cs @@ -28,7 +28,7 @@ public static IEnumerable GetGroupedMenus() Text = "Personal details", Controller = "MyAccount", Action = "Index", - IsActive = route => MatchRoute(route, "MyAccount", "PersonalDetails"), + IsActive = route => MatchRoute(route, "MyAccount", "Index"), }, new SideNavigationItem { diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountLocationViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountLocationViewModel.cs index 1e9a97b61..58bd757ae 100644 --- a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountLocationViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountLocationViewModel.cs @@ -2,8 +2,6 @@ { using System.Collections.Generic; using System.ComponentModel; - using System.ComponentModel.DataAnnotations; - using elfhHub.Nhs.Models.Common; using Microsoft.AspNetCore.Mvc.Rendering; using NHSUKViewComponents.Web.ViewModels; @@ -15,14 +13,12 @@ public class MyAccountLocationViewModel /// /// Gets or sets the country id. /// - [Required(ErrorMessage = "Select a country.")] [DisplayName("Country")] public int? SelectedCountryId { get; set; } /// /// Gets or sets the region id. /// - [Required(ErrorMessage = "Select a region.")] [DisplayName("Region")] public int? SelectedRegionId { get; set; } @@ -39,14 +35,17 @@ public class MyAccountLocationViewModel /// /// Gets or sets the country id. /// - ////[Required(ErrorMessage = "Select a country.")] - ////[DisplayName("Country")] public int? SelectedOtherCountryId { get; set; } /// /// Gets or sets a value indicating whether SelectedOtherCountry. /// - public bool SelectedOtherCountry { get; set; } + public bool HasSelectedOtherCountry { get; set; } + + /// + /// Gets or sets a value indicating whether SelectedRegion. + /// + public bool HasSelectedRegion { get; set; } /// /// Gets or sets the Country. diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountPersonalDetailsViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountPersonalDetailsViewModel.cs index 05abe5e71..194bcc89a 100644 --- a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountPersonalDetailsViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountPersonalDetailsViewModel.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using LearningHub.Nhs.WebUI.Attributes; using LearningHub.Nhs.WebUI.Helpers; + using LearningHub.Nhs.WebUI.Validation; /// /// Defines the . @@ -60,5 +61,15 @@ public class MyAccountPersonalDetailsViewModel /// Gets or sets the new primary email address. /// public string NewPrimaryEmailAddress { get; set; } + + /// + /// Gets or sets the SecondaryEmailAddress. + /// + [DataType(DataType.EmailAddress)] + [NotEqual("PrimaryEmailAddress", ErrorMessage = CommonValidationErrorMessages.SecondaryEmailShouldNotBeSame)] + [MaxLength(100, ErrorMessage = CommonValidationErrorMessages.TooLongEmail)] + [EmailAddress(ErrorMessage = CommonValidationErrorMessages.InvalidEmail)] + [NoWhitespace(ErrorMessage = CommonValidationErrorMessages.WhitespaceInEmail)] + public string SecondaryEmailAddress { get; set; } } } diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountSecurityViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountSecurityViewModel.cs index c27467f71..d1a26f4c1 100644 --- a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountSecurityViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAccountSecurityViewModel.cs @@ -27,10 +27,10 @@ public class MyAccountSecurityViewModel /// public string SecuritySecondQuestion { get; set; } - /// - /// Gets or sets the LastUpdated. - /// - public DateTimeOffset LastUpdated { get; set; } + /////// + /////// Gets or sets the LastUpdated. + /////// + ////public DateTimeOffset LastUpdated { get; set; } /// /// Gets or sets the PasswordHash. @@ -40,6 +40,6 @@ public class MyAccountSecurityViewModel /// /// Gets or sets the SecurityQuestionLastUpdated. /// - public DateTimeOffset? SecurityQuestionLastUpdated { get; set; } + public string SecurityQuestionLastUpdated { get; set; } } } diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/MyAcountSecurityQuestionsViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAcountSecurityQuestionsViewModel.cs new file mode 100644 index 000000000..7712ae470 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/MyAcountSecurityQuestionsViewModel.cs @@ -0,0 +1,54 @@ +namespace LearningHub.Nhs.WebUI.Models.UserProfile +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using elfhHub.Nhs.Models.Common; + using Microsoft.AspNetCore.Mvc.Rendering; + using NHSUKViewComponents.Web.ViewModels; + + /// + /// Defines the . + /// + public class MyAcountSecurityQuestionsViewModel + { + /// + /// Gets or sets selectedQuestion. + /// + public int SelectedFirstQuestionId { get; set; } + + /// + /// Gets or sets selectedQuestion. + /// + public int SelectedSecondQuestionId { get; set; } + + /// + /// Gets or sets the security question answer hash. + /// + public string SecurityFirstQuestionAnswerHash { get; set; } + + /// + /// Gets or sets the security question answer hash. + /// + public string SecuritySecondQuestionAnswerHash { get; set; } + + /// + /// Gets or sets the FirstSecurityQuestions. + /// + public IEnumerable FirstSecurityQuestions { get; set; } + + /// + /// Gets or sets the SecondSecurityQuestions. + /// + public IEnumerable SecondSecurityQuestions { get; set; } + + /// + /// Gets or sets the UserSecurityFirstQuestionId. + /// + public int UserSecurityFirstQuestionId { get; set; } + + /// + /// Gets or sets the UserSecurityFirstQuestionId. + /// + public int UserSecuritySecondQuestionId { get; set; } + } +} diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/UserPrimarySpecialtyUpdateViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/UserPrimarySpecialtyUpdateViewModel.cs index 8dd1546ed..5c91bd661 100644 --- a/LearningHub.Nhs.WebUI/Models/UserProfile/UserPrimarySpecialtyUpdateViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/UserPrimarySpecialtyUpdateViewModel.cs @@ -36,6 +36,21 @@ public class UserPrimarySpecialtyUpdateViewModel : PagingViewModel /// public GenericListViewModel OptionalSpecialtyItem { get; set; } + /// + /// Gets or sets the selected job role id. + /// + public int? SelectedJobRoleId { get; set; } + + /// + /// Gets or sets the selected grade id. + /// + public string SelectedGradeId { get; set; } + + /// + /// Gets or sets the selected medical council number. + /// + public string SelectedMedicalCouncilNo { get; set; } + /// /// sets the list of radio specialty. /// diff --git a/LearningHub.Nhs.WebUI/Models/UserProfile/UserStartDateUpdateViewModel.cs b/LearningHub.Nhs.WebUI/Models/UserProfile/UserStartDateUpdateViewModel.cs index 8d45071cd..372427781 100644 --- a/LearningHub.Nhs.WebUI/Models/UserProfile/UserStartDateUpdateViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/UserProfile/UserStartDateUpdateViewModel.cs @@ -30,6 +30,16 @@ public class UserStartDateUpdateViewModel : IValidatableObject /// public int? Year { get; set; } + /////// + /////// Gets or sets filter text. + /////// + ////public string FilterText { get; set; } + + /// + /// Gets or sets the selected primary specialty id. + /// + public int? SelectedPrimarySpecialtyId { get; set; } + /// /// Gets or sets the GetDate. /// diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notification.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notification.vue index 25ef12656..84fb2eb0c 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notification.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notification.vue @@ -1,7 +1,7 @@