From 9f585c383dc7735d8360a5a0dce5bab6d2501367 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Tue, 11 Mar 2025 21:55:02 +0000 Subject: [PATCH 01/23] TD-3711 Adding Last Accessed Columns To Tables --- .../202503111500_AddLastAccessedColumn.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs diff --git a/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs new file mode 100644 index 0000000000..f17a33bb19 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs @@ -0,0 +1,26 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202503111500)] + public class AddLastAccessedColumn : Migration + { + public override void Up() + { + Alter.Table("Users").AddColumn("LastAccessed").AsDateTime().Nullable(); + Alter.Table("DelegateAccounts").AddColumn("LastAccessed").AsDateTime().Nullable(); + Alter.Table("AdminAccounts").AddColumn("LastAccessed").AsDateTime().Nullable(); + + Execute.Sql("UPDATE u SET LastAccessed = (SELECT MAX(s.LoginTime) FROM DelegateAccounts da JOIN Sessions s ON da.ID = s.CandidateId WHERE da.UserID = u.ID) FROM users u;"); + Execute.Sql("UPDATE da SET LastAccessed = (SELECT MAX(s.LoginTime) FROM Sessions s WHERE s.CandidateId = da.ID) FROM DelegateAccounts da;"); + Execute.Sql("UPDATE da SET LastAccessed = (SELECT ca.LastAccessed FROM CandidateAssessments ca WHERE ca.ID = da.ID) FROM DelegateAccounts da where da.LastAccessed IS NULL;"); + Execute.Sql("UPDATE AA SET LastAccessed = (SELECT MAX(AdS.LoginTime) FROM AdminSessions AdS WHERE AdS.AdminID = AA.ID) FROM AdminAccounts AA;"); + } + public override void Down() + { + Delete.Column("LastAccessed").FromTable("Competencies"); + Delete.Column("LastAccessed").FromTable("DelegateAccounts"); + Delete.Column("LastAccessed").FromTable("AdminAccounts"); + } + } +} From 312ef004c9b6329b9b95e563a5062b06e341e585 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Wed, 12 Mar 2025 10:21:01 +0000 Subject: [PATCH 02/23] TD-3712 Update LastAccessed Date For Users During Login --- .../DataServices/LoginDataService.cs | 33 +++++++++++++++++++ .../Services/LoginServiceTests.cs | 4 ++- .../Controllers/LoginController.cs | 7 ++++ .../Services/LoginService.cs | 13 +++++++- DigitalLearningSolutions.Web/Startup.cs | 1 + 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 DigitalLearningSolutions.Data/DataServices/LoginDataService.cs diff --git a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs new file mode 100644 index 0000000000..93cd3abf6e --- /dev/null +++ b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs @@ -0,0 +1,33 @@ +namespace DigitalLearningSolutions.Data.DataServices +{ + using Dapper; + using System.Data; + + public interface ILoginDataService + { + void UpdateLastAccessedForUsersTable(int Id); + } + + public class LoginDataService : ILoginDataService + { + private readonly IDbConnection connection; + + public LoginDataService(IDbConnection connection) + { + this.connection = connection; + } + + public void UpdateLastAccessedForUsersTable(int Id) + { + connection.Execute( + @"UPDATE Users SET + LastAccessed = GETDATE() + WHERE ID = @Id", + new + { + Id + } + ); + } + } +} diff --git a/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs b/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs index f8dad9821e..a77c38f7b8 100644 --- a/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models; @@ -32,6 +33,7 @@ private static readonly (string?, List<(int centreId, string centreName, string private LoginService loginService = null!; private IUserService userService = null!; private IUserVerificationService userVerificationService = null!; + private ILoginDataService loginDataService = null!; [SetUp] public void Setup() @@ -39,7 +41,7 @@ public void Setup() userVerificationService = A.Fake(x => x.Strict()); userService = A.Fake(x => x.Strict()); - loginService = new LoginService(userService, userVerificationService); + loginService = new LoginService(userService, userVerificationService, loginDataService); } [Test] diff --git a/DigitalLearningSolutions.Web/Controllers/LoginController.cs b/DigitalLearningSolutions.Web/Controllers/LoginController.cs index 20280e18a8..284530a481 100644 --- a/DigitalLearningSolutions.Web/Controllers/LoginController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LoginController.cs @@ -82,6 +82,13 @@ public async Task Index(LoginViewModel model, string timeZone = " var loginResult = loginService.AttemptLogin(model.Username!.Trim(), model.Password!); + if (loginResult.LoginAttemptResult == LoginAttemptResult.LogIntoSingleCentre || + loginResult.LoginAttemptResult == LoginAttemptResult.ChooseACentre) + { + loginService.UpdateLastAccessedForUsersTable(loginResult.UserEntity.UserAccount.Id); + } + + switch (loginResult.LoginAttemptResult) { case LoginAttemptResult.InvalidCredentials: diff --git a/DigitalLearningSolutions.Web/Services/LoginService.cs b/DigitalLearningSolutions.Web/Services/LoginService.cs index 35d8cc7a16..6960e920cf 100644 --- a/DigitalLearningSolutions.Web/Services/LoginService.cs +++ b/DigitalLearningSolutions.Web/Services/LoginService.cs @@ -5,10 +5,12 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; + using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models; using DigitalLearningSolutions.Data.Models.User; + using DigitalLearningSolutions.Data.Utilities; using DigitalLearningSolutions.Data.ViewModels; using DigitalLearningSolutions.Web.Helpers; using Microsoft.AspNetCore.Authentication; @@ -28,6 +30,8 @@ List idsOfCentresWithUnverifiedEmails bool CentreEmailIsVerified(int userId, int centreIdIfLoggingIntoSingleCentre); + void UpdateLastAccessedForUsersTable(int Id); + Task HandleLoginResult( LoginResult loginResult, TicketReceivedContext context, @@ -41,11 +45,18 @@ public class LoginService : ILoginService { private readonly IUserService userService; private readonly IUserVerificationService userVerificationService; + private readonly ILoginDataService loginDataService; - public LoginService(IUserService userService, IUserVerificationService userVerificationService) + public LoginService(IUserService userService, IUserVerificationService userVerificationService, ILoginDataService loginDataService) { this.userService = userService; this.userVerificationService = userVerificationService; + this.loginDataService = loginDataService; + } + + public void UpdateLastAccessedForUsersTable(int Id) + { + loginDataService.UpdateLastAccessedForUsersTable(Id); } public LoginResult AttemptLogin(string username, string password) diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index bdc34b4d79..07b0c84237 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -534,6 +534,7 @@ private static void RegisterDataServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } private static void RegisterHelpers(IServiceCollection services) From 527973e5615ea36282903e00f6ae6ff8eefbcf75 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Wed, 12 Mar 2025 14:59:04 +0000 Subject: [PATCH 03/23] TD-3711 Correcting table name --- .../202503111500_AddLastAccessedColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs index f17a33bb19..95a0eb6020 100644 --- a/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs +++ b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs @@ -18,7 +18,7 @@ public override void Up() } public override void Down() { - Delete.Column("LastAccessed").FromTable("Competencies"); + Delete.Column("LastAccessed").FromTable("Users"); Delete.Column("LastAccessed").FromTable("DelegateAccounts"); Delete.Column("LastAccessed").FromTable("AdminAccounts"); } From 52cb242344678d83bd821699769e29bff2e6bed0 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Wed, 12 Mar 2025 15:45:31 +0000 Subject: [PATCH 04/23] TD-3712 Correcting GetUTCDate() --- DigitalLearningSolutions.Data/DataServices/LoginDataService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs index 93cd3abf6e..37faf67778 100644 --- a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs @@ -21,7 +21,7 @@ public void UpdateLastAccessedForUsersTable(int Id) { connection.Execute( @"UPDATE Users SET - LastAccessed = GETDATE() + LastAccessed = GetUtcDate() WHERE ID = @Id", new { From 66d8aed518392d12a7d58693548e4c5e6fc5a388 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 13 Mar 2025 15:57:27 +0000 Subject: [PATCH 05/23] TD-3713- Update LastAccessed Date For AdminAccounts And DelegateAccounts --- .../DataServices/LoginDataService.cs | 30 +++++++++++++++++++ .../Controllers/LoginController.cs | 6 ++-- .../Services/LoginService.cs | 14 +++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs index 37faf67778..109d5a9273 100644 --- a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs @@ -6,6 +6,10 @@ public interface ILoginDataService { void UpdateLastAccessedForUsersTable(int Id); + + void UpdateLastAccessedForDelegatesAccountsTable(int Id); + + void UpdateLastAccessedForAdminAccountsTable(int Id); } public class LoginDataService : ILoginDataService @@ -29,5 +33,31 @@ public void UpdateLastAccessedForUsersTable(int Id) } ); } + + public void UpdateLastAccessedForDelegatesAccountsTable(int Id) + { + connection.Execute( + @"UPDATE DelegateAccounts SET + LastAccessed = GetUtcDate() + WHERE ID = @Id", + new + { + Id + } + ); + } + + public void UpdateLastAccessedForAdminAccountsTable(int Id) + { + connection.Execute( + @"UPDATE AdminAccounts SET + LastAccessed = GetUtcDate() + WHERE ID = @Id", + new + { + Id + } + ); + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/LoginController.cs b/DigitalLearningSolutions.Web/Controllers/LoginController.cs index 284530a481..f9fb365dff 100644 --- a/DigitalLearningSolutions.Web/Controllers/LoginController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LoginController.cs @@ -226,11 +226,13 @@ int centreIdToLogInto IsPersistent = rememberMe, IssuedUtc = clockUtility.UtcNow, }; + var centreAccountSet = userEntity?.GetCentreAccountSet(centreIdToLogInto); + loginService.UpdateLastAccessedForDelegatesAccountsTable(centreAccountSet.DelegateAccount.Id); - var adminAccount = userEntity!.GetCentreAccountSet(centreIdToLogInto)?.AdminAccount; - + var adminAccount = centreAccountSet?.AdminAccount; if (adminAccount?.Active == true) { + loginService.UpdateLastAccessedForAdminAccountsTable(adminAccount.Id); sessionService.StartAdminSession(adminAccount.Id); } diff --git a/DigitalLearningSolutions.Web/Services/LoginService.cs b/DigitalLearningSolutions.Web/Services/LoginService.cs index 6960e920cf..dc52aca0b5 100644 --- a/DigitalLearningSolutions.Web/Services/LoginService.cs +++ b/DigitalLearningSolutions.Web/Services/LoginService.cs @@ -32,6 +32,10 @@ List idsOfCentresWithUnverifiedEmails void UpdateLastAccessedForUsersTable(int Id); + void UpdateLastAccessedForDelegatesAccountsTable(int Id); + + void UpdateLastAccessedForAdminAccountsTable(int Id); + Task HandleLoginResult( LoginResult loginResult, TicketReceivedContext context, @@ -59,6 +63,16 @@ public void UpdateLastAccessedForUsersTable(int Id) loginDataService.UpdateLastAccessedForUsersTable(Id); } + public void UpdateLastAccessedForDelegatesAccountsTable(int Id) + { + loginDataService.UpdateLastAccessedForDelegatesAccountsTable(Id); + } + + public void UpdateLastAccessedForAdminAccountsTable(int Id) + { + loginDataService.UpdateLastAccessedForAdminAccountsTable(Id); + } + public LoginResult AttemptLogin(string username, string password) { var userEntity = userService.GetUserByUsername(username); From 01774db0ec21281d0c406d6849d054f140324ee1 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 13 Mar 2025 16:55:29 +0000 Subject: [PATCH 06/23] TD-3713 Addressing test fail for the case when only admin account is present --- DigitalLearningSolutions.Web/Controllers/LoginController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Controllers/LoginController.cs b/DigitalLearningSolutions.Web/Controllers/LoginController.cs index f9fb365dff..968535144d 100644 --- a/DigitalLearningSolutions.Web/Controllers/LoginController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LoginController.cs @@ -227,7 +227,11 @@ int centreIdToLogInto IssuedUtc = clockUtility.UtcNow, }; var centreAccountSet = userEntity?.GetCentreAccountSet(centreIdToLogInto); - loginService.UpdateLastAccessedForDelegatesAccountsTable(centreAccountSet.DelegateAccount.Id); + + if (centreAccountSet?.DelegateAccount?.Id != null) + { + loginService.UpdateLastAccessedForDelegatesAccountsTable(centreAccountSet.DelegateAccount.Id); + } var adminAccount = centreAccountSet?.AdminAccount; if (adminAccount?.Active == true) From be28d6d96b84e9a46fcc3c9a4145c187cfa0c956 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 13 Mar 2025 17:15:01 +0000 Subject: [PATCH 07/23] TD-3714 Adding LastAccessed Date To Delegates Card In Tracking System --- .../DelegateUserCardDataService.cs | 1 + .../Models/User/DelegateAccount.cs | 1 + .../Models/User/DelegateUser.cs | 1 + .../Models/User/DelegateUserCard.cs | 1 + .../Delegates/Shared/DelegateInfoViewModel.cs | 5 + .../_SearchableDelegateCard.cshtml | 159 +++++++++--------- 6 files changed, 92 insertions(+), 76 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs index 8e2a424ed5..40556ef3cb 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs @@ -87,6 +87,7 @@ FROM DelegateAccounts AS da c.CentreName, da.CentreID, da.DateRegistered, + da.LastAccessed, da.RegistrationConfirmationHash, c.Active AS CentreActive, COALESCE(ucd.Email, u.PrimaryEmail) AS EmailAddress, diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs index 3f720e350b..55dc4b25cc 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs @@ -12,6 +12,7 @@ public class DelegateAccount public bool CentreActive { get; set; } public string CandidateNumber { get; set; } = string.Empty; public DateTime DateRegistered { get; set; } + public DateTime LastAccessed { get; set; } public string? Answer1 { get; set; } public string? Answer2 { get; set; } public string? Answer3 { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs b/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs index 7033b8d13f..f007b355b1 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs @@ -9,6 +9,7 @@ public class DelegateUser : User public int UserId { get; set; } public string CandidateNumber { get; set; } = string.Empty; public DateTime? DateRegistered { get; set; } + public DateTime? LastAccessed { get; set; } public int JobGroupId { get; set; } public string? JobGroupName { get; set; } public string? Answer1 { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs b/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs index ed04402308..1e9caf4e9d 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs @@ -23,6 +23,7 @@ public DelegateUserCard(DelegateEntity delegateEntity) Password = delegateEntity.UserAccount.PasswordHash; CandidateNumber = delegateEntity.DelegateAccount.CandidateNumber; DateRegistered = delegateEntity.DelegateAccount.DateRegistered; + LastAccessed = delegateEntity.DelegateAccount.LastAccessed; JobGroupId = delegateEntity.UserAccount.JobGroupId; JobGroupName = delegateEntity.UserAccount.JobGroupName; Answer1 = delegateEntity.DelegateAccount.Answer1; diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs index 504fd4b454..814e488ac9 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs @@ -41,6 +41,10 @@ IEnumerable delegateRegistrationPrompts { RegistrationDate = delegateUser.DateRegistered.Value.ToString(DateHelper.StandardDateFormat); } + if (delegateUser.LastAccessed.HasValue) + { + LastAccessed = delegateUser.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } DelegateRegistrationPrompts = delegateRegistrationPrompts; RegistrationConfirmationHash = delegateUser.RegistrationConfirmationHash; @@ -61,6 +65,7 @@ IEnumerable delegateRegistrationPrompts public int JobGroupId { get; set; } public string? JobGroup { get; set; } public string? RegistrationDate { get; set; } + public string? LastAccessed { get; set; } public string ProfessionalRegistrationNumber { get; set; } public string? RegistrationConfirmationHash { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/AllDelegates/_SearchableDelegateCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/AllDelegates/_SearchableDelegateCard.cshtml index 13f17d0ae7..4064cd80bd 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/AllDelegates/_SearchableDelegateCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/AllDelegates/_SearchableDelegateCard.cshtml @@ -4,91 +4,98 @@
-
- - - @Model.DelegateInfo.TitleName - @if (Model.DelegateInfo.Email == null) - @("(Email address not set)") - else - @DisplayStringHelper.GetEmailDisplayString(Model.DelegateInfo.Email) +
+ + + @Model.DelegateInfo.TitleName + @if (Model.DelegateInfo.Email == null) + @("(Email address not set)") + else + @DisplayStringHelper.GetEmailDisplayString(Model.DelegateInfo.Email) - - + +
-
+
- + -
-
-
- Name -
- -
+
+
+
+ Name +
+ +
-
-
- Email -
- -
+
+
+ Email +
+ +
-
-
- ID -
- -
+
+
+ ID +
+ +
-
-
- Registration date -
- - -
+
+
+ Registration date +
+ + +
-
-
- Job group -
- - -
+
+
+ Last Accessed date +
+ +
-
-
- Professional Registration Number -
- -
+
+
+ Job group +
+ + +
- @foreach (var delegateRegistrationPrompt in Model.DelegateInfo.DelegateRegistrationPrompts) - { -
-
@delegateRegistrationPrompt.Prompt
- - @if (Model.RegistrationPromptFilters.ContainsKey(delegateRegistrationPrompt.PromptNumber)) - { - - } -
- } -
+
+
+ Professional Registration Number +
+ +
- Manage delegate - - Set password - -
-
+ @foreach (var delegateRegistrationPrompt in Model.DelegateInfo.DelegateRegistrationPrompts) + { +
+
@delegateRegistrationPrompt.Prompt
+ + @if (Model.RegistrationPromptFilters.ContainsKey(delegateRegistrationPrompt.PromptNumber)) + { + + } +
+ } + + + Manage delegate + + Set password + +
+ From 5d4dd9c10ceb4d6abbb9ec7d91a304126dbcf3cb Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 13 Mar 2025 21:11:08 +0000 Subject: [PATCH 08/23] TD-3716-Adding LastAccessed Date In Admin Card --- .../DataServices/UserDataService/AdminUserDataService.cs | 1 + DigitalLearningSolutions.Data/Models/User/AdminAccount.cs | 3 +++ DigitalLearningSolutions.Data/Models/User/AdminEntity.cs | 2 ++ .../Centre/Administrator/SearchableAdminViewModel.cs | 8 ++++++++ .../Centre/Administrator/_SearchableAdminCard.cshtml | 8 ++++++++ 5 files changed, 22 insertions(+) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs index d93094c97b..9378fcc04c 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs @@ -105,6 +105,7 @@ FROM AdminAccounts AS aa aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, + aa.LastAccessed, CASE WHEN aa.CategoryID IS NULL THEN 'All' ELSE cc.CategoryName diff --git a/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs b/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs index eaff1da52f..51a109785b 100644 --- a/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs @@ -1,5 +1,7 @@ namespace DigitalLearningSolutions.Data.Models.User { + using System; + public class AdminAccount { public int Id { get; set; } @@ -26,6 +28,7 @@ public class AdminAccount public bool IsWorkforceContributor { get; set; } public bool IsLocalWorkforceManager { get; set; } public bool IsNominatedSupervisor { get; set; } + public DateTime? LastAccessed { get; set; } public bool IsCmsAdministrator => ImportOnly && IsContentManager; public bool IsCmsManager => IsContentManager && !ImportOnly; diff --git a/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs b/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs index e070aca124..a9b71e5143 100644 --- a/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs +++ b/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs @@ -3,6 +3,7 @@ using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Centres; using DigitalLearningSolutions.Data.Models.SearchSortFilterPaginate; + using System; public class AdminEntity : BaseSearchableItem { @@ -72,6 +73,7 @@ public override string SearchableName public bool IsSuperAdmin => AdminAccount.IsSuperAdmin; public bool IsReportsViewer => AdminAccount.IsReportsViewer; public bool IsActive => AdminAccount.Active; + public DateTime? LastAccessed => AdminAccount.LastAccessed; } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs index 7e4ab30b47..c3da68c395 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs @@ -5,6 +5,8 @@ using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; + using System; + using DateHelper = Helpers.DateHelper; public class SearchableAdminViewModel : BaseFilterableViewModel { @@ -23,6 +25,10 @@ ReturnPageQuery returnPageQuery EmailAddress = admin.EmailForCentreNotifications; IsLocked = admin.UserAccount.FailedLoginCount >= AuthHelper.FailedLoginThreshold; IsActive = admin.AdminAccount.Active; + if (admin.LastAccessed.HasValue) + { + LastAccessed = admin.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } CanShowDeactivateAdminButton = UserPermissionsHelper.LoggedInAdminCanDeactivateUser(admin.AdminAccount, loggedInAdminAccount); @@ -46,6 +52,8 @@ ReturnPageQuery returnPageQuery public bool IsActive { get; set; } + public string? LastAccessed { get; set; } + public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Administrator/_SearchableAdminCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Administrator/_SearchableAdminCard.cshtml index 420378260a..fe44f17379 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Administrator/_SearchableAdminCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Administrator/_SearchableAdminCard.cshtml @@ -35,6 +35,14 @@ @Model.CategoryName +
+
+ Last accessed +
+
+ @(Model.LastAccessed != null ? Model.LastAccessed : "-") +
+
@if (Model.IsLocked) From 6b805b7ba3a4f1f31d73b4742a877d5b39a1791c Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Fri, 14 Mar 2025 08:57:06 +0000 Subject: [PATCH 09/23] TD-3717 Adding LastAccessed Date In SuperAdmin UserAccounts Card --- .../DataServices/UserDataService/UserDataService.cs | 1 + DigitalLearningSolutions.Data/Models/User/UserAccount.cs | 1 + .../SuperAdmin/Users/SearchableUserAccountViewModel.cs | 5 +++++ .../Views/SuperAdmin/Users/_SearchableUserCard.cshtml | 6 ++++++ 4 files changed, 13 insertions(+) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs index aba887ccf5..fdc974094b 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs @@ -308,6 +308,7 @@ public partial class UserDataService : IUserDataService u.ProfessionalRegistrationNumber, u.ProfileImage, u.Active, + u.LastAccessed, u.ResetPasswordID, u.TermsAgreed, u.FailedLoginCount, diff --git a/DigitalLearningSolutions.Data/Models/User/UserAccount.cs b/DigitalLearningSolutions.Data/Models/User/UserAccount.cs index 4e1c975bf1..6afca34e14 100644 --- a/DigitalLearningSolutions.Data/Models/User/UserAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/UserAccount.cs @@ -14,6 +14,7 @@ public class UserAccount public string? ProfessionalRegistrationNumber { get; set; } public byte[]? ProfileImage { get; set; } public bool Active { get; set; } + public DateTime? LastAccessed { get; set; } public int? ResetPasswordId { get; set; } public DateTime? TermsAgreed { get; set; } public int FailedLoginCount { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs index 5b1e7e63ac..263e008165 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs @@ -27,6 +27,10 @@ ReturnPageQuery returnPageQuery ProfessionalRegistrationNumber = user.UserAccount.ProfessionalRegistrationNumber; LearningHubAuthId = user.UserAccount.LearningHubAuthId; ReturnPageQuery = returnPageQuery; + if (user.UserAccount.LastAccessed.HasValue) + { + LastAccessed = user.UserAccount.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } } public int Id { get; set; } @@ -51,6 +55,7 @@ ReturnPageQuery returnPageQuery public int? LearningHubAuthId { get; set; } + public string? LastAccessed { get; set; } public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/SuperAdmin/Users/_SearchableUserCard.cshtml b/DigitalLearningSolutions.Web/Views/SuperAdmin/Users/_SearchableUserCard.cshtml index 959e8c2060..521b421106 100644 --- a/DigitalLearningSolutions.Web/Views/SuperAdmin/Users/_SearchableUserCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/SuperAdmin/Users/_SearchableUserCard.cshtml @@ -86,6 +86,12 @@ +
+
+ Last Accessed +
+ +
Job Group From 1f17db28f9d3ea8b2a4f66c82d4bbe7dcd72bd39 Mon Sep 17 00:00:00 2001 From: rshrirohit <126668828+rshrirohit@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:01:39 +0000 Subject: [PATCH 10/23] TD-3714 Making LastAccessed date nullable in model --- DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs index 55dc4b25cc..66d458fd13 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs @@ -12,7 +12,7 @@ public class DelegateAccount public bool CentreActive { get; set; } public string CandidateNumber { get; set; } = string.Empty; public DateTime DateRegistered { get; set; } - public DateTime LastAccessed { get; set; } + public DateTime? LastAccessed { get; set; } public string? Answer1 { get; set; } public string? Answer2 { get; set; } public string? Answer3 { get; set; } From 94bb54bf7aa10a9a12951b82ae75081b48d94df7 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Fri, 14 Mar 2025 16:03:14 +0000 Subject: [PATCH 11/23] TD-3715 Including LastAccessed Date In Delegates Export --- .../UserDataService/DelegateUserCardDataService.cs | 1 + .../Services/DelegateDownloadFileService.cs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs index 40556ef3cb..6d08cfc6ae 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs @@ -125,6 +125,7 @@ FROM AdminAccounts aa c.CentreName, da.CentreID, da.DateRegistered, + da.LastAccessed, da.RegistrationConfirmationHash, c.Active AS CentreActive, COALESCE(ucd.Email, u.PrimaryEmail) AS EmailAddress, diff --git a/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs b/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs index 7274173d88..e53ee74932 100644 --- a/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs +++ b/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs @@ -39,6 +39,7 @@ public class DelegateDownloadFileService : IDelegateDownloadFileService private const string ProfessionalRegistrationNumber = "Professional Registration Number"; private const string JobGroup = "Job group"; private const string RegisteredDate = "Registered"; + private const string LastAccessed = "Last Accessed"; private const string RegistrationComplete = "Registration complete"; private const string Active = "Active"; private const string Approved = "Approved"; @@ -333,6 +334,7 @@ DataTable dataTable new DataColumn(ProfessionalRegistrationNumber), new DataColumn(JobGroup), new DataColumn(RegisteredDate), + new DataColumn(LastAccessed), } ); @@ -374,7 +376,7 @@ CentreRegistrationPrompts registrationPrompts ); row[JobGroup] = delegateRecord.JobGroupName; row[RegisteredDate] = delegateRecord.DateRegistered?.Date; - + row[LastAccessed] = delegateRecord.LastAccessed?.Date; var delegateAnswers = delegateRecord.GetRegistrationFieldAnswers(); foreach (var prompt in registrationPrompts.CustomPrompts) @@ -402,7 +404,7 @@ CentreRegistrationPrompts registrationPrompts private static void FormatAllDelegateWorksheetColumns(IXLWorkbook workbook, DataTable dataTable) { ClosedXmlHelper.FormatWorksheetColumn(workbook, dataTable, RegisteredDate, XLDataType.DateTime); - + ClosedXmlHelper.FormatWorksheetColumn(workbook, dataTable, LastAccessed, XLDataType.DateTime); var boolColumns = new[] { RegistrationComplete, Active, Approved, IsAdmin }; foreach (var columnName in boolColumns) { From 1f0d1da79b5c81972cb792418fd3e116324c383d Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Sun, 16 Mar 2025 14:32:47 +0000 Subject: [PATCH 12/23] TD-3718 Adding LastAccessed Date in SuperAdmin Administrators Card --- .../DataServices/UserDataService/AdminUserDataService.cs | 2 +- .../Administrators/SearchableAdminAccountsViewModel.cs | 8 ++++++-- .../AdminAccounts/_SearchableAdminAccountsCard.cshtml | 8 ++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs index 9378fcc04c..9f293ad394 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs @@ -370,7 +370,7 @@ public IEnumerable GetAdminAccountsByUserId(int userId) } string BaseSelectQuery = $@"SELECT aa.ID, aa.UserID, aa.CentreID, aa.Active, aa.IsCentreAdmin, aa.IsReportsViewer, aa.IsSuperAdmin, aa.IsCentreManager, - aa.IsContentManager, aa.IsContentCreator, aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, aa.IsFrameworkDeveloper, aa.IsFrameworkContributor,aa.ImportOnly, + aa.LastAccessed, aa.IsContentManager, aa.IsContentCreator, aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, aa.IsFrameworkDeveloper, aa.IsFrameworkContributor,aa.ImportOnly, aa.IsWorkforceManager, aa.IsWorkforceContributor, aa.IsLocalWorkforceManager, aa.IsNominatedSupervisor, u.ID, u.PrimaryEmail, u.FirstName, u.LastName, u.Active, u.FailedLoginCount, c.CentreID, c.CentreName, diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs index 0afcfe7498..88f5ea2889 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs @@ -5,7 +5,7 @@ using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; - + using DateHelper = Helpers.DateHelper; public class SearchableAdminAccountsViewModel : BaseFilterableViewModel { public readonly bool CanShowDeleteAdminButton; @@ -28,7 +28,10 @@ ReturnPageQuery returnPageQuery IsLocked = admin.UserAccount?.FailedLoginCount >= AuthHelper.FailedLoginThreshold; IsAdminActive = admin.AdminAccount.Active; IsUserActive = admin.UserAccount.Active; - + if (admin.LastAccessed.HasValue) + { + LastAccessed = admin.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } CanShowDeactivateAdminButton = IsAdminActive && admin.AdminIdReferenceCount > 0; CanShowDeleteAdminButton = admin.AdminIdReferenceCount == 0; @@ -47,6 +50,7 @@ ReturnPageQuery returnPageQuery public bool IsLocked { get; set; } public bool IsAdminActive { get; set; } public bool IsUserActive { get; set; } + public string? LastAccessed { get; set; } public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/SuperAdmin/AdminAccounts/_SearchableAdminAccountsCard.cshtml b/DigitalLearningSolutions.Web/Views/SuperAdmin/AdminAccounts/_SearchableAdminAccountsCard.cshtml index be0fc62d7f..50946491a4 100644 --- a/DigitalLearningSolutions.Web/Views/SuperAdmin/AdminAccounts/_SearchableAdminAccountsCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/SuperAdmin/AdminAccounts/_SearchableAdminAccountsCard.cshtml @@ -74,6 +74,14 @@
+
+
+ Last Accessed +
+ +
+
+
User Account From 30bebd048333412d6bbffdb350f64d74c8def559 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Tue, 18 Mar 2025 09:25:44 +0000 Subject: [PATCH 13/23] TD-3719 Including LastAccessed Date In SuperAdmin Delegates Card --- .../DataServices/UserDataService/UserDataService.cs | 1 + .../Models/SuperAdmin/SuperAdminDelegateAccount.cs | 1 + .../SuperAdmin/Delegates/SearchableDelegatesViewModel.cs | 2 ++ .../SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml | 8 ++++++++ 4 files changed, 12 insertions(+) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs index fdc974094b..178e2f62dc 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs @@ -620,6 +620,7 @@ public void UpdateUserDetailsAccount(string firstName, string lastName, string p ce.CentreName, ce.Active AS CentreActive, da.DateRegistered, + da.LastAccessed, da.CandidateNumber, da.Approved, da.SelfReg, diff --git a/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs b/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs index a18268f104..aac978b35a 100644 --- a/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs +++ b/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs @@ -19,6 +19,7 @@ public SuperAdminDelegateAccount(DelegateEntity delegateEntity) LearningHubAuthId = delegateEntity.UserAccount.LearningHubAuthId; RegistrationConfirmationHash = delegateEntity.DelegateAccount.RegistrationConfirmationHash; DateRegistered = delegateEntity.DelegateAccount.DateRegistered; + LastAccessed = delegateEntity.DelegateAccount.LastAccessed; SelfReg = delegateEntity.DelegateAccount.SelfReg; Active = delegateEntity.DelegateAccount.Active; EmailVerified = delegateEntity.UserAccount.EmailVerified; diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs index 8ea0ed22e5..a615461e73 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs @@ -28,6 +28,7 @@ ReturnPageQuery returnPageQuery LearningHubID = delegates.LearningHubAuthId; AccountClaimed = delegates.RegistrationConfirmationHash; DateRegistered = delegates.DateRegistered?.ToString(Data.Helpers.DateHelper.StandardDateFormat); + LastAccessed = delegates.LastAccessed?.ToString(Data.Helpers.DateHelper.StandardDateFormat); SelRegistered = delegates.SelfReg; IsDelegateActive = delegates.Active; IsCentreEmailVerified = delegates.CentreEmailVerified == null ? false : true; @@ -52,6 +53,7 @@ ReturnPageQuery returnPageQuery public bool IsLocked { get; set; } public string AccountClaimed { get; set; } public string? DateRegistered { get; set; } + public string? LastAccessed { get; set; } public bool SelRegistered { get; set; } public bool IsDelegateActive { get; set; } public bool IsUserActive { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml b/DigitalLearningSolutions.Web/Views/SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml index be45052cc3..21e74a2e1a 100644 --- a/DigitalLearningSolutions.Web/Views/SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml @@ -162,6 +162,14 @@
+
+
+ Last Accessed +
+ +
+
+
Self registered From 57df4e115091324ddf6da3caaadc0dcc4621f982 Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Thu, 20 Mar 2025 17:15:57 +0000 Subject: [PATCH 14/23] TD-5443-Added check for existing flag names prior to add or edit custom flags --- .../FrameworksController/Frameworks.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs index 2cdda2862c..f66bc6427f 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs @@ -582,14 +582,33 @@ public IActionResult EditFrameworkFlag(CustomFlagViewModel model, int frameworkI { if (ModelState.IsValid) { + var flags = frameworkService.GetCustomFlagsByFrameworkId(frameworkId, null) + .Where(fn => fn.FlagName == model.FlagName).ToList(); + + bool nameExists = flags.Any(x => x.FlagName == model.FlagName); + bool idExists = flags.Any(x => x.FlagId == flagId); + if (actionname == "Edit") { - frameworkService.UpdateFrameworkCustomFlag(frameworkId, model.Id, model.FlagName, model.FlagGroup, model.FlagTagClass); + if (nameExists && !idExists) + { + ModelState.AddModelError(nameof(model.FlagName), "A custom flag already exists."); + return View("Developer/EditCustomFlag", model); + } + else + frameworkService.UpdateFrameworkCustomFlag(frameworkId, model.Id, model.FlagName, model.FlagGroup, model.FlagTagClass); } else { - frameworkService.AddCustomFlagToFramework(frameworkId, model.FlagName, model.FlagGroup, model.FlagTagClass); + if (nameExists) + { + ModelState.AddModelError(nameof(model.FlagName), "A custom flag already exists."); + return View("Developer/EditCustomFlag", model); + } + else + frameworkService.AddCustomFlagToFramework(frameworkId, model.FlagName, model.FlagGroup, model.FlagTagClass); } + return RedirectToAction("EditFrameworkFlags", "Frameworks", new { frameworkId }); } return View("Developer/EditCustomFlag", model); From 87d38416ee489dc976de938826d25a6f2bba2b2f Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Fri, 21 Mar 2025 11:35:34 +0000 Subject: [PATCH 15/23] TD-5435-The 'To Do' section has been moved above the 'Staff' section. --- .../Views/Supervisor/Index.cshtml | 164 +++++++++--------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Supervisor/Index.cshtml b/DigitalLearningSolutions.Web/Views/Supervisor/Index.cshtml index db4fcd24c2..07b71ef446 100644 --- a/DigitalLearningSolutions.Web/Views/Supervisor/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Supervisor/Index.cshtml @@ -3,99 +3,99 @@ @model SupervisorDashboardViewModel; @{ - ViewData["Title"] = "Dashboard"; - ViewData["Application"] = "Supervisor"; - ViewData["HeaderPathName"] = "Supervisor"; + ViewData["Title"] = "Dashboard"; + ViewData["Application"] = "Supervisor"; + ViewData["HeaderPathName"] = "Supervisor"; } @section NavMenuItems { - + } - @section NavBreadcrumbs { - +@section NavBreadcrumbs { + } -

Supervisor Dashboard

- @if (Model.BannerText != null) +

Supervisor Dashboard

+

Your to do list

+@if (Model.SupervisorDashboardToDoItems.Count() > 0) { -

@Model.BannerText

-} - -

Your to do list

-@if (Model.SupervisorDashboardToDoItems.Count() > 0) -{ - + else if (toDoItem.ResultsReviewRequest) + { +
  • + + Confirm self-assessment results for @toDoItem.DelegateName's @toDoItem.ProfileName + . Requested @toDoItem.Requested.ToShortDateString() +
  • + } + } + } else { -

    There are no tasks requiring your attention.

    +

    There are no tasks requiring your attention.

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

    @Model.BannerText

    } + From 9c892a45e0f14463fd27a40023bad1a20628547c Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Fri, 21 Mar 2025 14:15:33 +0000 Subject: [PATCH 16/23] TD-5436-The 'To Do' section has been moved above the 'Frameworks' section. --- .../Views/Frameworks/Index.cshtml | 149 +++++++++--------- 1 file changed, 75 insertions(+), 74 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Index.cshtml index 127cb973db..8bb58fd4d5 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Index.cshtml @@ -3,91 +3,92 @@ @model DashboardViewModel; @{ - ViewData["Title"] = "Dashboard"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; + ViewData["Title"] = "Dashboard"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; } @section NavMenuItems { - + } - @section NavBreadcrumbs { - -} -

    Frameworks Dashboard

    -

    Welcome @Model.Username. You are accessing as a @(Model.IsFrameworkDeveloper ? "Framework Developer" : Model.IsFrameworkContributor ? "Framework Contributor" : "Framework Viewer") and a @(Model.IsWorkforceManager ? "Workforce Manager" : Model.IsWorkforceContributor ? "Role Profile Contributor" : "Role Profile Viewer").

    - + +} +

    Frameworks Dashboard

    +

    Welcome @Model.Username. You are accessing as a @(Model.IsFrameworkDeveloper ? "Framework Developer" : Model.IsFrameworkContributor ? "Framework Contributor" : "Framework Viewer") and a @(Model.IsWorkforceManager ? "Workforce Manager" : Model.IsWorkforceContributor ? "Role Profile Contributor" : "Role Profile Viewer").

    Your to do list

    @if (Model.DashboardToDoItems.Count() > 0) { - } else { -

    There are no tasks requiring your attention.

    +

    There are no tasks requiring your attention.

    } + + From fffa334d7ee0743d616cd97e207cbb33b8658ddb Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Thu, 27 Mar 2025 14:54:10 +0000 Subject: [PATCH 17/23] TD-5438-Moved the 'View Proficiencies' button to the top. --- .../SelfAssessmentDescription.cshtml | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml index 346322f8e3..bad5f5af78 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml @@ -29,23 +29,6 @@ { } -@if (Model.IsSupervised) -{ -
    -
    - @(Html.Raw(Model.Description)) -
    -
    - -
    -
    -} -else -{ -
    @(Html.Raw(Model.Description))
    -} - @if (Model.LinearNavigation) { View @Model.VocabPlural @@ -72,6 +55,19 @@ else Continue where I left off } } - - - +@if (Model.IsSupervised) +{ +
    +
    + @(Html.Raw(Model.Description)) +
    +
    + +
    +
    +} +else +{ +
    @(Html.Raw(Model.Description))
    +} From d7bbaa9f92dea953f5525976b6949ef39000e5c3 Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Tue, 1 Apr 2025 15:52:03 +0100 Subject: [PATCH 18/23] TD-5439 - A competency description is added to competency when exists. --- .../Supervisor/VerifyMultipleResults.cshtml | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Supervisor/VerifyMultipleResults.cshtml b/DigitalLearningSolutions.Web/Views/Supervisor/VerifyMultipleResults.cshtml index 97dc08a7fa..6b4acba585 100644 --- a/DigitalLearningSolutions.Web/Views/Supervisor/VerifyMultipleResults.cshtml +++ b/DigitalLearningSolutions.Web/Views/Supervisor/VerifyMultipleResults.cshtml @@ -130,9 +130,25 @@ @competency.Vocabulary
    -
    From 6623b9053c1144366b501820b1695f999dc2de9b Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Thu, 3 Apr 2025 15:34:17 +0100 Subject: [PATCH 19/23] TD-5482-Related role added to view --- .../Supervisor/Shared/_DelegateProfileAssessmentGrid.cshtml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Supervisor/Shared/_DelegateProfileAssessmentGrid.cshtml b/DigitalLearningSolutions.Web/Views/Supervisor/Shared/_DelegateProfileAssessmentGrid.cshtml index 7e5b1e87e9..d638ca4ac8 100644 --- a/DigitalLearningSolutions.Web/Views/Supervisor/Shared/_DelegateProfileAssessmentGrid.cshtml +++ b/DigitalLearningSolutions.Web/Views/Supervisor/Shared/_DelegateProfileAssessmentGrid.cshtml @@ -12,7 +12,7 @@ Self Assessment - Role links + Role Last activity @@ -36,9 +36,7 @@ Self Assessment @delegateSelfAssessment.RoleName - Role links @(delegateSelfAssessment.ProfessionalGroup != null ? delegateSelfAssessment.ProfessionalGroup : "None/Generic") - @(delegateSelfAssessment.SubGroup != null ? " / " + delegateSelfAssessment.SubGroup : "") - @(delegateSelfAssessment.RoleProfile != null ? " / " + delegateSelfAssessment.RoleProfile : "") + Role@(delegateSelfAssessment.SupervisorRoleTitle) Last activity @delegateSelfAssessment.LastAccessed.ToShortDateString()
    (@delegateSelfAssessment.LaunchCount launches) From 7a001a4a35404d23e56fd0ba764b0af6aa3b6bdd Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Mon, 7 Apr 2025 16:12:25 +0100 Subject: [PATCH 20/23] TD-5481-Added a Details view component in the view to show supervisor list. --- .../delegates/courseDelegates.scss | 28 +++++++++++++++++ .../_DelegateSelfAssessmentDetails.cshtml | 30 ++++++++++++++----- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss index 3f796924e8..47fb8f20d5 100644 --- a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss +++ b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss @@ -26,3 +26,31 @@ } } } + +.nhsuk-expander[open] details.nhsuk-details[open] .nhsuk-details__summary-text::before { + display: block; + width: 0; + height: 0; + border-style: solid; + border-color: transparent; + clip-path: polygon(0% 0%, 50% 100%, 100% 0%); + border-width: 12.124px 7px 0 7px; + border-top-color: inherit; +} + +.nhsuk-details[open] .nhsuk-details .nhsuk-details__summary-text::before { + bottom: 0; + content: ""; + left: 0; + margin: auto; + position: absolute; + top: 0; + display: block; + width: 0; + height: 0; + border-style: solid; + border-color: transparent; + clip-path: polygon(0% 0%, 100% 50%, 0% 100%); + border-width: 7px 0 7px 12.124px; + border-left-color: inherit; +} diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateSelfAssessmentDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateSelfAssessmentDetails.cshtml index b05ac0e229..a5376e461d 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateSelfAssessmentDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateSelfAssessmentDetails.cshtml @@ -45,14 +45,28 @@
    @if (Model.Supervisors.Any()) { - @foreach (var supervisor in Model.Supervisors.Take(3)) - { -

    @supervisor.SupervisorName, @supervisor.RoleName (@supervisor.CentreName)

    - } - @if (Model.Supervisors.Count() > 3) - { -

    +@(Model.Supervisors.Count() - 3) more

    - } +
    + + @{ + var supervisors = string.Join(", ", new[] { + new { Role = "Educator/Manager", Count = Model.Supervisors.Count(x => x.RoleName == "Educator/Manager") }, + new { Role = "Assessor", Count = Model.Supervisors.Count(x => x.RoleName == "Assessor") }, + new { Role = "Supervisor", Count = Model.Supervisors.Count(x => x.RoleName == "Supervisor") } + } + .Where(x => x.Count > 0) + .Select(x => $"{x.Count} {x.Role}{(x.Count > 1 ? "s" : "")}")); + } + @supervisors + +
    +
      + @foreach (var supervisor in Model.Supervisors) + { +
    • @supervisor.SupervisorName, @supervisor.RoleName (@supervisor.CentreName)
    • + } +
    +
    +
    } else { From 3821eb400224b65d7d5b01df589dc1bef93f63c0 Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Thu, 10 Apr 2025 12:47:39 +0100 Subject: [PATCH 21/23] TD-5511-Framework query modified- The admin userid is used to check the framework owner --- .../DataServices/FrameworkDataService.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index b3176c1bec..bdd381cfe3 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -277,13 +277,22 @@ public class FrameworkDataService : IFrameworkDataService OwnerAdminID, (SELECT Forename + ' ' + Surname + (CASE WHEN Active = 1 THEN '' ELSE ' (Inactive)' END) AS Expr1 FROM AdminUsers WHERE (AdminID = FW.OwnerAdminID)) AS Owner, BrandID, - CategoryID, + FW.CategoryID, TopicID, CreatedDate, PublishStatusID, UpdatedByAdminID, (SELECT Forename + ' ' + Surname + (CASE WHEN Active = 1 THEN '' ELSE ' (Inactive)' END) AS Expr1 FROM AdminUsers AS AdminUsers_1 WHERE (AdminID = FW.UpdatedByAdminID)) AS UpdatedBy, - CASE WHEN FW.OwnerAdminID = @adminId THEN 3 WHEN fwc.CanModify = 1 THEN 2 WHEN fwc.CanModify = 0 THEN 1 ELSE 0 END AS UserRole, + CASE + WHEN (aa.UserID = (SELECT UserID FROM AdminAccounts WHERE ID = @adminId)) THEN 3 + WHEN (fwc.CanModify = 1) OR + (SELECT COUNT(*) + FROM FrameworkCollaborators fc + JOIN AdminAccounts aa1 ON fc.AdminID = aa1.ID + WHERE fc.FrameworkID = fw.ID + AND fc.CanModify = 1 AND fc.IsDeleted = 0 + AND aa1.UserID = (SELECT aa2.UserID FROM AdminAccounts aa2 WHERE aa2.ID = 12842)) > 0 THEN 2 + WHEN fwc.CanModify = 0 THEN 1 ELSE 0 END AS UserRole, fwr.ID AS FrameworkReviewID"; private const string BrandedFrameworkFields = @@ -304,9 +313,9 @@ FROM CourseTopics private const string FlagFields = @"fl.ID AS FlagId, fl.FrameworkId, fl.FlagName, fl.FlagGroup, fl.FlagTagClass"; private const string FrameworkTables = - @"Frameworks AS FW LEFT OUTER JOIN - FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId AND COALESCE(IsDeleted, 0) = 0 - LEFT OUTER JOIN FrameworkReviews AS fwr ON fwc.ID = fwr.FrameworkCollaboratorID AND fwr.Archived IS NULL AND fwr.ReviewComplete IS NULL"; + @"Frameworks AS FW INNER JOIN AdminAccounts AS aa ON aa.ID = fw.OwnerAdminID + LEFT OUTER JOIN FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId AND COALESCE(IsDeleted, 0) = 0 + LEFT OUTER JOIN FrameworkReviews AS fwr ON fwc.ID = fwr.FrameworkCollaboratorID AND fwr.Archived IS NULL AND fwr.ReviewComplete IS NULL"; private const string AssessmentQuestionFields = @"SELECT AQ.ID, AQ.Question, AQ.MinValue, AQ.MaxValue, AQ.AssessmentQuestionInputTypeID, AQI.InputTypeName, AQ.AddedByAdminId, CASE WHEN AQ.AddedByAdminId = @adminId THEN 1 ELSE 0 END AS UserIsOwner, AQ.CommentsPrompt, AQ.CommentsHint"; @@ -707,7 +716,7 @@ FROM [FrameworkCompetencies] new { competencyId, frameworkCompetencyGroupID } ); } - if(addDefaultQuestions) + if (addDefaultQuestions) { AddDefaultQuestionsToCompetency(competencyId, frameworkId); } From 43875c6630216b416864690e9aef7a0d499376f6 Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Wed, 16 Apr 2025 15:37:23 +0100 Subject: [PATCH 22/23] TD-5514-Stored proc updated to add course name in the email subject --- ...endExpiredTBCReminders_AppendCourseName.cs | 19 ++++++++ .../Properties/Resources.Designer.cs | 44 ++++++++++++++++++ .../Properties/Resources.resx | 6 +++ ...514-Alter_SendExpiredTBCReminders_Down.sql | Bin 0 -> 16774 bytes ...-5514-Alter_SendExpiredTBCReminders_Up.sql | Bin 0 -> 17190 bytes 5 files changed, 69 insertions(+) create mode 100644 DigitalLearningSolutions.Data.Migrations/2025041161520_Alter_SendExpiredTBCReminders_AppendCourseName.cs create mode 100644 DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Down.sql create mode 100644 DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Up.sql diff --git a/DigitalLearningSolutions.Data.Migrations/2025041161520_Alter_SendExpiredTBCReminders_AppendCourseName.cs b/DigitalLearningSolutions.Data.Migrations/2025041161520_Alter_SendExpiredTBCReminders_AppendCourseName.cs new file mode 100644 index 0000000000..02bcb19b10 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/2025041161520_Alter_SendExpiredTBCReminders_AppendCourseName.cs @@ -0,0 +1,19 @@ + + +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(2025041161520)] + public class Alter_SendExpiredTBCReminders_AppendCourseName : Migration + { + public override void Up() + { + Execute.Sql(Properties.Resources.TD_5514_Alter_SendExpiredTBCReminders_Up); + } + public override void Down() + { + Execute.Sql(Properties.Resources.TD_5514_Alter_SendExpiredTBCReminders_Down); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs index 1b4edf4c6f..ea8a97cc31 100644 --- a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs +++ b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs @@ -2436,6 +2436,50 @@ internal static string TD_5412_Alter_SendExpiredTBCReminders_Up { } } + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[SendExpiredTBCReminders] Script Date: 16/04/2025 10:50:12 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 17/08/2018 + ///-- Description: Uses DB mail to send reminders to delegates on courses with a TBC date within 1 month. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[SendExpiredTBCReminders] + /// -- Add the parameters for the stor [rest of string was truncated]";. + /// + internal static string TD_5514_Alter_SendExpiredTBCReminders_Down { + get { + return ResourceManager.GetString("TD_5514_Alter_SendExpiredTBCReminders_Down", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[SendExpiredTBCReminders] Script Date: 16/04/2025 10:50:12 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 17/08/2018 + ///-- Description: Uses DB mail to send reminders to delegates on courses with a TBC date within 1 month. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[SendExpiredTBCReminders] + /// -- Add the parameters for the stor [rest of string was truncated]";. + /// + internal static string TD_5514_Alter_SendExpiredTBCReminders_Up { + get { + return ResourceManager.GetString("TD_5514_Alter_SendExpiredTBCReminders_Up", resourceCulture); + } + } + /// /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[GetActiveAvailableCustomisationsForCentreFiltered_V6] Script Date: 29/09/2022 19:11:04 ******/ ///SET ANSI_NULLS ON diff --git a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx index a951c296c9..df856a17ae 100644 --- a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx +++ b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx @@ -475,4 +475,10 @@ ..\Scripts\TD-5412-Alter_SendExpiredTBCReminders_Up.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + ..\Scripts\TD-5514-Alter_SendExpiredTBCReminders_Down.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD-5514-Alter_SendExpiredTBCReminders_Up.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + \ No newline at end of file diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Down.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Down.sql new file mode 100644 index 0000000000000000000000000000000000000000..f80def01d5ff0c4cedc2a066ab52ffb65b3177ad GIT binary patch literal 16774 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 literal 0 HcmV?d00001 diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Up.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Up.sql new file mode 100644 index 0000000000000000000000000000000000000000..448ba429466edd4b3cc9f6b6bdfc8262b591842c GIT binary patch literal 17190 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 O#BKJg6z=>#L;N3@`e#i5 literal 0 HcmV?d00001 From 120c5dd0601a35582982f251902c29cc2b194dc7 Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Thu, 17 Apr 2025 17:11:56 +0100 Subject: [PATCH 23/23] TD-5451- Empty competency description check added --- .../LearningPortal/SelfAssessments/_OverviewTable.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_OverviewTable.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_OverviewTable.cshtml index 7c51b25169..be40c4aa32 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_OverviewTable.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_OverviewTable.cshtml @@ -49,7 +49,7 @@ model="competency.CompetencyFlags" />
    - @if (competency.Description != null && !competency.AlwaysShowDescription) + @if (!string.IsNullOrWhiteSpace(competency.Description) && !competency.AlwaysShowDescription) {
    @@ -64,10 +64,10 @@ } else { -

    +

    @competency.Name

    - @if (competency.Description != null) + @if (!string.IsNullOrWhiteSpace(competency.Description)) {

    @(Html.Raw(competency.Description))