diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs index 91613e27d3..5e04b9b019 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs @@ -239,7 +239,10 @@ public void GetCourseStatisticsAtCentreForCategoryID_should_return_course_statis DelegateCount = 25, AllAttempts = 49, AttemptsPassed = 34, - CompletedCount = 5 + CompletedCount = 5, + HideInLearnerPortal = false, + CategoryName = "Office 2007", + LearningMinutes = "N/A" }; result.Should().HaveCount(260); diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index c7a48a6028..9eb65f2d3c 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -170,10 +170,14 @@ public IEnumerable GetCourseStatisticsAtCentreForCategoryId(in {DelegateCountQuery}, {CompletedCountQuery}, {AllAttemptsQuery}, - {AttemptsPassedQuery} + {AttemptsPassedQuery}, + cu.HideInLearnerPortal, + cc.CategoryName, + cu.LearningTimeMins AS LearningMinutes FROM dbo.Customisations AS cu INNER JOIN dbo.CentreApplications AS ca ON ca.ApplicationID = cu.ApplicationID INNER JOIN dbo.Applications AS ap ON ap.ApplicationID = ca.ApplicationID + INNER JOIN dbo.CourseCategories AS cc ON cc.CourseCategoryID = ap.CourseCategoryID WHERE (ap.CourseCategoryID = @categoryId OR @categoryId = 0) AND (cu.CentreID = @centreId OR (cu.AllCentres = 1 AND ca.Active = 1)) AND ca.CentreID = @centreId diff --git a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs index 53092894e1..9b5831c9ef 100644 --- a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs +++ b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs @@ -15,6 +15,9 @@ public class CourseStatistics public int InProgressCount => DelegateCount - CompletedCount; public int AllAttempts { get; set; } public int AttemptsPassed { get; set; } + public bool HideInLearnerPortal { get; set; } + public string CategoryName { get; set; } + public string LearningMinutes { get; set; } public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) ? ApplicationName diff --git a/DigitalLearningSolutions.Web/DigitalLearningSolutions.Web.csproj b/DigitalLearningSolutions.Web/DigitalLearningSolutions.Web.csproj index c73e11e42f..7bcfb90855 100644 --- a/DigitalLearningSolutions.Web/DigitalLearningSolutions.Web.csproj +++ b/DigitalLearningSolutions.Web/DigitalLearningSolutions.Web.csproj @@ -38,6 +38,7 @@ + @@ -96,6 +97,7 @@ + diff --git a/DigitalLearningSolutions.Web/Helpers/ConfigHelper.cs b/DigitalLearningSolutions.Web/Helpers/ConfigHelper.cs index da723a509a..63e0c94c94 100644 --- a/DigitalLearningSolutions.Web/Helpers/ConfigHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/ConfigHelper.cs @@ -10,6 +10,7 @@ public static class ConfigHelper public const string DefaultConnectionStringName = "DefaultConnection"; public const string UnitTestConnectionStringName = "UnitTestConnection"; public const string CurrentSystemBaseUrlName = "CurrentSystemBaseUrl"; + public const string AppRootPathName = "AppRootPath"; public const string MapsApiKey = "MapsAPIKey"; public static IConfigurationRoot GetAppConfig() diff --git a/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseFilterOptions.cs b/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseFilterOptions.cs new file mode 100644 index 0000000000..1e0527b352 --- /dev/null +++ b/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseFilterOptions.cs @@ -0,0 +1,37 @@ +namespace DigitalLearningSolutions.Web.Helpers.FilterOptions +{ + using DigitalLearningSolutions.Data.Models.Courses; + using DigitalLearningSolutions.Web.Models.Enums; + using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; + + public static class CourseStatusFilterOptions + { + private const string Group = "Status"; + + public static readonly FilterOptionViewModel IsInactive = new FilterOptionViewModel( + "Inactive", + Group + FilteringHelper.Separator + nameof(CourseStatistics.Active) + FilteringHelper.Separator + "false", + FilterStatus.Warning + ); + + public static readonly FilterOptionViewModel IsActive = new FilterOptionViewModel( + "Active", + Group + FilteringHelper.Separator + nameof(CourseStatistics.Active) + FilteringHelper.Separator + "true", + FilterStatus.Success + ); + + public static readonly FilterOptionViewModel IsHiddenInLearningPortal = new FilterOptionViewModel( + "Hidden in Learning Portal", + Group + FilteringHelper.Separator + nameof(CourseStatistics.HideInLearnerPortal) + + FilteringHelper.Separator + "true", + FilterStatus.Warning + ); + + public static readonly FilterOptionViewModel IsNotHiddenInLearningPortal = new FilterOptionViewModel( + "Visible in Learning Portal", + Group + FilteringHelper.Separator + nameof(CourseStatistics.HideInLearnerPortal) + + FilteringHelper.Separator + "true", + FilterStatus.Success + ); + } +} diff --git a/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs b/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs index 477f3173c5..cc9460eb34 100644 --- a/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs @@ -1,6 +1,7 @@ namespace DigitalLearningSolutions.Web.Helpers { using System.Collections.Generic; + using DigitalLearningSolutions.Data.Models.Courses; using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers.FilterOptions; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; @@ -10,7 +11,7 @@ public static class FilterableTagHelper public static IEnumerable GetCurrentTagsForAdminUser(AdminUser adminUser) { var tags = new List(); - + if (adminUser.IsLocked) { tags.Add(new SearchableTagViewModel(AdminAccountStatusFilterOptions.IsLocked)); @@ -52,5 +53,32 @@ public static IEnumerable GetCurrentTagsForAdminUser(Adm return tags; } + + public static IEnumerable GetCurrentTagsForCourseStatistics( + CourseStatistics courseStatistics + ) + { + var tags = new List(); + + if (courseStatistics.Active) + { + tags.Add(new SearchableTagViewModel(CourseStatusFilterOptions.IsActive)); + } + else + { + tags.Add(new SearchableTagViewModel(CourseStatusFilterOptions.IsInactive)); + } + + if (courseStatistics.HideInLearnerPortal) + { + tags.Add(new SearchableTagViewModel(CourseStatusFilterOptions.IsHiddenInLearningPortal)); + } + else + { + tags.Add(new SearchableTagViewModel(CourseStatusFilterOptions.IsNotHiddenInLearningPortal)); + } + + return tags; + } } } diff --git a/DigitalLearningSolutions.Web/Scripts/trackingSystem/centreCourseSetup.ts b/DigitalLearningSolutions.Web/Scripts/trackingSystem/centreCourseSetup.ts new file mode 100644 index 0000000000..46f74db022 --- /dev/null +++ b/DigitalLearningSolutions.Web/Scripts/trackingSystem/centreCourseSetup.ts @@ -0,0 +1,55 @@ +const copyCourseLinkClass = 'copy-course-button'; +const copyLinkIdPrefix = 'copy-course-'; +const launchCourseButtonIdPrefix = 'launch-course-'; + +setUpCourseLinkClipboardCopiers(); + +function setUpCourseLinkClipboardCopiers() { + const copyCourseLinks = Array.from(document.getElementsByClassName(copyCourseLinkClass)); + + copyCourseLinks.forEach( + (currentLink) => { + const linkId = currentLink.id; + const customisationId = linkId.slice(copyLinkIdPrefix.length); + currentLink.addEventListener('click', () => copyLaunchCourseLinkToClipboard(customisationId)); + }, + ); +} + +function copyLaunchCourseLinkToClipboard(customisationId: string) { + const launchCourseButtonId = launchCourseButtonIdPrefix + customisationId; + const launchCourseButton = document.getElementById(launchCourseButtonId) as HTMLAnchorElement; + const link = launchCourseButton.href; + const succeeded = copyTextToClipboard(link); + const alertMessage = succeeded + ? `Copied the text: ${link}` + : `Copy not supported or blocked. Try manually selecting and copying: ${link}`; + + alert(alertMessage); +} + +function copyTextToClipboard(textToCopy: string): boolean { + try { + navigator.clipboard.writeText(textToCopy); + return true; + } catch (e) { + return copyTextToClipboardFallback(textToCopy); + } +} + +function copyTextToClipboardFallback(textToCopy: string): boolean { + const hiddenInput = document.body.appendChild(document.createElement('input')); + hiddenInput.value = textToCopy; + hiddenInput.select(); + hiddenInput.setSelectionRange(0, textToCopy.length); + let succeeded: boolean; + + try { + succeeded = document.execCommand('copy'); + } catch (e) { + succeeded = false; + } + + document.body.removeChild(hiddenInput); + return succeeded; +} diff --git a/DigitalLearningSolutions.Web/Styles/index.scss b/DigitalLearningSolutions.Web/Styles/index.scss index 0e441992eb..3919fd7784 100644 --- a/DigitalLearningSolutions.Web/Styles/index.scss +++ b/DigitalLearningSolutions.Web/Styles/index.scss @@ -45,6 +45,14 @@ ul > li > ul > li { display: block; } +.js-only-inline { + display: none; +} + +.js-enabled .js-only-inline { + display: inline; +} + .small-edit-button, a.small-edit-button { margin-bottom: nhsuk-spacing(2); margin-top: nhsuk-spacing(0); diff --git a/DigitalLearningSolutions.Web/Styles/learningMenu/contentViewer.scss b/DigitalLearningSolutions.Web/Styles/learningMenu/contentViewer.scss index 5e28c1b83a..95c0a4dfd1 100644 --- a/DigitalLearningSolutions.Web/Styles/learningMenu/contentViewer.scss +++ b/DigitalLearningSolutions.Web/Styles/learningMenu/contentViewer.scss @@ -33,11 +33,3 @@ .hidden { display: none !important; } - -.js-only-inline { - display: none; -} - -.js-enabled .js-only-inline { - display: inline; -} diff --git a/DigitalLearningSolutions.Web/Styles/trackingSystem/courseSetup.scss b/DigitalLearningSolutions.Web/Styles/trackingSystem/courseSetup.scss index 9642c57edd..0745e440f5 100644 --- a/DigitalLearningSolutions.Web/Styles/trackingSystem/courseSetup.scss +++ b/DigitalLearningSolutions.Web/Styles/trackingSystem/courseSetup.scss @@ -1,3 +1,18 @@ @import "~nhsuk-frontend/packages/core/all"; @import "../shared/cardWithButtons"; @import "../shared/searchableElements/searchableElements"; + +.copy-course-button{ + background: none; + border: none; + padding: 0; + color: $nhsuk-link-color; + @extend .nhsuk-u-font-size-19; + text-decoration: underline; + + &:hover{ + color: $nhsuk-link-hover-color; + text-decoration: none; + cursor: pointer; + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/SearchableCourseStatisticsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/SearchableCourseStatisticsViewModel.cs index 7311b4d726..29e097f770 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/SearchableCourseStatisticsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/SearchableCourseStatisticsViewModel.cs @@ -1,28 +1,39 @@ namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup { + using System; using DigitalLearningSolutions.Data.Models.Courses; + using DigitalLearningSolutions.Web.Helpers; + using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; - public class SearchableCourseStatisticsViewModel + public class SearchableCourseStatisticsViewModel : BaseFilterableViewModel { public SearchableCourseStatisticsViewModel(CourseStatistics courseStatistics) { CustomisationId = courseStatistics.CustomisationId; - CentreId = courseStatistics.CentreId; - Active = courseStatistics.Active; DelegateCount = courseStatistics.DelegateCount; - CompletedCount = courseStatistics.CompletedCount; InProgressCount = courseStatistics.InProgressCount; - PassRate = courseStatistics.PassRate; CourseName = courseStatistics.CourseName; + CategoryName = courseStatistics.CategoryName; + LearningMinutes = courseStatistics.LearningMinutes; + Tags = FilterableTagHelper.GetCurrentTagsForCourseStatistics(courseStatistics); } public int CustomisationId { get; set; } - public int CentreId { get; set; } - public bool Active { get; set; } public int DelegateCount { get; set; } - public int CompletedCount { get; set; } public int InProgressCount { get; set; } public string CourseName { get; set; } - public double PassRate { get; set; } + public string CategoryName { get; set; } + public string LearningMinutes { get; set; } + + public string EmailHref => GenerateEmailHref(); + + private string GenerateEmailHref() + { + var launchCourseLink = + $"{ConfigHelper.GetAppConfig()[ConfigHelper.AppRootPathName]}/LearningMenu/{CustomisationId}"; + var subject = Uri.EscapeDataString($"Digital Learning Solutions Course Link - {CourseName}"); + var content = Uri.EscapeDataString($"To start your {CourseName} course, go to {launchCourseLink}"); + return $"mailto:?subject={subject}&body={content}"; + } } } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/Index.cshtml index dc77129c8e..a5e4f51349 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/Index.cshtml @@ -4,7 +4,6 @@ @model CourseSetupViewModel - @{ ViewData["Title"] = "Centre course setup"; ViewData["Application"] = "Tracking System"; @@ -25,10 +24,14 @@ } else {
@foreach (var course in Model.Courses) { - + }
} + +@section scripts { + +} diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/_CentreCourseCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/_CentreCourseCard.cshtml index c01d5145ad..b74f5ccaf9 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/_CentreCourseCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/_CentreCourseCard.cshtml @@ -8,7 +8,57 @@ @Model.CourseName +
+ + +
+
+
+ Category +
+
+ @Model.CategoryName +
+
+ +
+
+ Learning minutes +
+
+ @Model.LearningMinutes +
+
+ +
+
+ Total delegates +
+
+ @Model.DelegateCount +
+
+ +
+
+ In progress +
+
+ @Model.InProgressCount +
+
+ +
+ +

Want to share the course?

+ Generate email + +

To share this course with others, click Launch Course and copy the URL to the clipboard.

@@ -17,6 +67,7 @@