Skip to content

Commit ce7e4fb

Browse files
committed
TD-5153 Implements page changes and starts to implement download actions
1 parent fa509b9 commit ce7e4fb

File tree

6 files changed

+149
-60
lines changed

6 files changed

+149
-60
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace DigitalLearningSolutions.Data.Models.Frameworks.Import
8+
{
9+
public class BulkCompetency
10+
{
11+
public int? id { get; set; }
12+
public string? CompetencyGroup { get; set; }
13+
public string? GroupDescription { get; set; }
14+
public string? Competency { get; set; }
15+
public string? CompetencyDescription { get; set; }
16+
public bool? AlwaysShowDescription { get; set; }
17+
public string? FlagsCsv { get; set; }
18+
}
19+
}

DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public enum RowStatus
1212
CompetencyGroupAndCompetencyInserted,
1313
CompetencyInserted
1414
}
15-
public class CompetencyTableRow
15+
public class CompetencyTableRow : BulkCompetency
1616
{
1717
public CompetencyTableRow(IXLTable table, IXLRangeRow row)
1818
{
@@ -23,28 +23,25 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row)
2323
}
2424

2525
RowNumber = row.RowNumber();
26-
CompetencyGroupName = FindFieldValue("competency group");
27-
CompetencyName = FindFieldValue("competency name");
26+
CompetencyGroup = FindFieldValue("competency group");
27+
Competency = FindFieldValue("competency name");
2828
CompetencyDescription = FindFieldValue("competency description");
2929
RowStatus = RowStatus.NotYetProcessed;
3030
}
3131
public int RowNumber { get; set; }
32-
public string? CompetencyGroupName { get; set; }
33-
public string? CompetencyName { get; set; }
34-
public string? CompetencyDescription { get; set; }
3532
public ImportCompetenciesResult.ErrorReason? Error { get; set; }
3633
public RowStatus RowStatus { get; set; }
3734
public bool Validate()
3835
{
39-
if (string.IsNullOrEmpty(CompetencyName))
36+
if (string.IsNullOrEmpty(Competency))
4037
{
4138
Error = ImportCompetenciesResult.ErrorReason.MissingCompetencyName;
4239
}
43-
else if (CompetencyGroupName?.Length > 255)
40+
else if (CompetencyGroup?.Length > 255)
4441
{
4542
Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName;
4643
}
47-
else if (CompetencyName.Length > 500)
44+
else if (Competency.Length > 500)
4845
{
4946
Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyName;
5047
}

DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.AspNetCore.Mvc;
1111
using Microsoft.Extensions.Logging;
1212
using GDS.MultiPageFormData;
13+
using DigitalLearningSolutions.Data.Utilities;
1314

1415
[Authorize(Policy = CustomPolicies.UserFrameworksAdminOnly)]
1516
[SetDlsSubApplication(nameof(DlsSubApplication.Frameworks))]
@@ -25,6 +26,7 @@ public partial class FrameworksController : Controller
2526
private readonly ILearningHubApiClient learningHubApiClient;
2627
private readonly ISearchSortFilterPaginateService searchSortFilterPaginateService;
2728
private readonly IMultiPageFormService multiPageFormService;
29+
private readonly IClockUtility clockUtility;
2830

2931
public FrameworksController(
3032
IFrameworkService frameworkService,
@@ -35,7 +37,8 @@ public FrameworksController(
3537
ICompetencyLearningResourcesService competencyLearningResourcesService,
3638
ILearningHubApiClient learningHubApiClient,
3739
ISearchSortFilterPaginateService searchSortFilterPaginateService,
38-
IMultiPageFormService multiPageFormService
40+
IMultiPageFormService multiPageFormService,
41+
IClockUtility clockUtility
3942
)
4043
{
4144
this.frameworkService = frameworkService;
@@ -47,6 +50,7 @@ IMultiPageFormService multiPageFormService
4750
this.learningHubApiClient = learningHubApiClient;
4851
this.searchSortFilterPaginateService = searchSortFilterPaginateService;
4952
this.multiPageFormService = multiPageFormService;
53+
this.clockUtility = clockUtility;
5054
}
5155

5256
private int? GetCentreId()

DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using DigitalLearningSolutions.Data.Exceptions;
2+
using DigitalLearningSolutions.Data.Utilities;
3+
using DigitalLearningSolutions.Web.Helpers;
4+
using DigitalLearningSolutions.Web.Services;
25
using DigitalLearningSolutions.Web.ViewModels.Frameworks;
36
using Microsoft.AspNetCore.Mvc;
47

@@ -19,6 +22,19 @@ public IActionResult ImportCompetencies(int frameworkId, string tabname)
1922
};
2023
return View("Developer/ImportCompetencies", model);
2124
}
25+
[Route("DownloadDelegates")]
26+
public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption)
27+
{
28+
string fileName = DownloadOption == 2 ? $"DLS Competencies for Bulk Update {clockUtility.UtcToday:yyyy-MM-dd}.xlsx" : "DLS Delegates for Bulk Registration.xlsx";
29+
var content = importCompetenciesFromFileService.GetCompetencyFileForFramework(
30+
frameworkId, DownloadOption == 2 ? false : true
31+
);
32+
return File(
33+
content,
34+
FileHelper.GetContentTypeFromFileName(fileName),
35+
fileName
36+
);
37+
}
2238
[HttpPost]
2339
[Route("/Framework/{frameworkId}/{tabname}/Import")]
2440
public IActionResult StartImport(ImportCompetenciesViewModel model)

DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
namespace DigitalLearningSolutions.Web.Services
66
{
77
using System.Collections.Generic;
8+
using System.IO;
89
using System.Linq;
910
using ClosedXML.Excel;
1011
using DigitalLearningSolutions.Data.DataServices;
1112
using DigitalLearningSolutions.Data.Exceptions;
13+
using DigitalLearningSolutions.Data.Helpers;
14+
using DigitalLearningSolutions.Data.Models.Centres;
1215
using DigitalLearningSolutions.Data.Models.Frameworks.Import;
1316
using Microsoft.AspNetCore.Http;
1417

1518
public interface IImportCompetenciesFromFileService
1619
{
17-
20+
byte[] GetCompetencyFileForFramework(int frameworkId, bool v);
1821
public ImportCompetenciesResult ProcessCompetenciesFromFile(IFormFile file, int adminUserId, int frameworkId);
1922
}
2023
public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService
@@ -115,5 +118,18 @@ private static bool ValidateHeaders(IXLTable table)
115118
var actualHeaders = table.Fields.Select(x => x.Name.ToLower()).OrderBy(x => x);
116119
return actualHeaders.SequenceEqual(expectedHeaders);
117120
}
121+
122+
public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank)
123+
{
124+
using var workbook = new XLWorkbook();
125+
PopulateCompetenciesSheet(workbook, frameworkId, blank);
126+
if (blank)
127+
{
128+
ClosedXmlHelper.HideWorkSheetColumn(workbook, "DelegateID");
129+
}
130+
using var stream = new MemoryStream();
131+
workbook.SaveAs(stream);
132+
return stream.ToArray();
133+
}
118134
}
119135
}

DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml

Lines changed: 86 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,96 @@
22
@using DigitalLearningSolutions.Web.ViewModels.Frameworks
33
@model ImportCompetenciesViewModel
44
@{
5-
ViewData["Title"] = "Framework - Import Competencies";
6-
ViewData["Application"] = "Framework Service";
7-
ViewData["HeaderPathName"] = "Framework Service";
8-
var errorHasOccurred = !ViewData.ModelState.IsValid;
9-
var cancelLinkData = Html.GetRouteValues();
5+
ViewData["Application"] = "Framework Service";
6+
ViewData["HeaderPathName"] = "Framework Service";
7+
var errorHasOccurred = !ViewData.ModelState.IsValid;
8+
ViewData["Title"] = errorHasOccurred ? "Error: Bulk upload competencies" : "Bulk upload competencies";
9+
var cancelLinkData = Html.GetRouteValues();
1010
}
1111
<link rel="stylesheet" href="@Url.Content("~/css/frameworks/frameworksShared.css")" asp-append-version="true">
1212
@section NavMenuItems {
13-
<partial name="Shared/_NavMenuItems" />
13+
<partial name="Shared/_NavMenuItems" />
1414
}
15-
@section NavBreadcrumbs {
16-
<nav class="nhsuk-breadcrumb" aria-label="Breadcrumb">
17-
<div class="nhsuk-width-container">
18-
<ol class="nhsuk-breadcrumb__list">
19-
<li class="nhsuk-breadcrumb__item"><a class="nhsuk-breadcrumb__link trigger-loader" asp-action="ViewFrameworks" asp-route-tabname="Mine">Frameworks</a></li>
20-
<li class="nhsuk-breadcrumb__item"><a class="nhsuk-breadcrumb__link trigger-loader" asp-action="ViewFramework" asp-route-frameworkId="@ViewContext.RouteData.Values["frameworkId"]" asp-route-tabname="Structure">Framework Structure</a></li>
21-
<li class="nhsuk-breadcrumb__item">Excel import</li>
22-
</ol>
23-
<p class="nhsuk-breadcrumb__back"><a class="nhsuk-breadcrumb__backlink" asp-action="ViewFramework" asp-route-frameworkId="@ViewContext.RouteData.Values["frameworkId"]" asp-route-tabname="Structure">Back to framework structure</a></p>
24-
</div>
25-
</nav>
15+
@section NavBreadcrumbs {
16+
<nav class="nhsuk-breadcrumb" aria-label="Breadcrumb">
17+
<div class="nhsuk-width-container">
18+
<ol class="nhsuk-breadcrumb__list">
19+
<li class="nhsuk-breadcrumb__item"><a class="nhsuk-breadcrumb__link trigger-loader" asp-action="ViewFrameworks" asp-route-tabname="Mine">Frameworks</a></li>
20+
<li class="nhsuk-breadcrumb__item"><a class="nhsuk-breadcrumb__link trigger-loader" asp-action="ViewFramework" asp-route-frameworkId="@ViewContext.RouteData.Values["frameworkId"]" asp-route-tabname="Structure">Framework Structure</a></li>
21+
<li class="nhsuk-breadcrumb__item">Bulk upload</li>
22+
</ol>
23+
<p class="nhsuk-breadcrumb__back"><a class="nhsuk-breadcrumb__backlink" asp-action="ViewFramework" asp-route-frameworkId="@ViewContext.RouteData.Values["frameworkId"]" asp-route-tabname="Structure">Back to framework structure</a></p>
24+
</div>
25+
</nav>
2626
}
27-
<div class="nhsuk-grid-row">
27+
<div class="nhsuk-grid-row">
2828
<div class="nhsuk-grid-column-full">
29-
@if (errorHasOccurred)
30-
{
31-
<vc:error-summary order-of-property-names="@(new[] { nameof(Model.ImportFile) })" />
32-
}
33-
<h1 class="nhsuk-heading-xl">Import from Excel</h1>
34-
<vc:inset-text css-class="" text="The building blocks for your framework will be referred to as 'competencies' for the purpose of importing. Once they have been imported, they will be referred to using your chosen vocabulary."></vc:inset-text>
35-
<p class="nhsuk-body-m">
36-
To import competencies from Excel, you will need an .xlsx worksheet file saved to your computer that meets the following requirements:
37-
<ul>
38-
<li>Has import data formatted as a table on the first worksheet in the file</li>
39-
<li>
40-
Has the following column titles on the cells in the first row:
41-
<ul>
42-
<li><strong>Competency group</strong> (cell A1)</li>
43-
<li><strong>Competency name</strong> (cell B1)</li>
44-
<li><strong>Competency description</strong> (cell C1)</li>
45-
</ul>
46-
</li>
47-
<li>Import data in subsequent rows with no blank rows (including hidden rows)</li>
48-
<li>Import data must be formatted as a table (select all data and choose "Format as Table" in Excel</li>
49-
<li>Import data rows must include a <strong>Competency name</strong></li>
50-
<li>If <strong>Competency group</strong> is left blank, the competency will be imported without a group and listed after grouped competencies in the framework</li>
51-
</ul>
52-
</p>
53-
<form class="nhsuk-u-margin-bottom-3" method="post" enctype="multipart/form-data">
54-
<input type="hidden" asp-for="@Model.FrameworkId" />
55-
<vc:file-input asp-for="@nameof(Model.ImportFile)" label="Excel file ready for import" hint-text="" css-class="nhsuk-u-width-one-half" />
56-
<button class="nhsuk-button" type="submit">Upload and import</button>
57-
</form>
58-
<vc:cancel-link asp-controller="Frameworks" asp-action="ViewFramework" asp-all-route-data="@cancelLinkData" link-text="Cancel" />
59-
</div>
29+
@if (errorHasOccurred)
30+
{
31+
<vc:error-summary order-of-property-names="@(new[] { nameof(Model.ImportFile) })" />
32+
}
33+
<h1 class="nhsuk-heading-xl">Bulk upload or update competencies</h1>
34+
<vc:inset-text css-class="" text="The building blocks for your framework will be referred to as 'competencies' for the purpose of importing. Once they have been imported, they will be referred to using your chosen vocabulary."></vc:inset-text>
35+
<p class="nhsuk-body-m">
36+
To bulk add and/or update competencies in your framework, download an Excel workbook using one of the options below.
37+
</p>
38+
<form>
39+
<div class="nhsuk-form-group">
40+
41+
<fieldset class="nhsuk-fieldset" aria-describedby="download-hint">
42+
<legend class="nhsuk-fieldset__legend nhsuk-fieldset__legend--l">
43+
<h1 class="nhsuk-fieldset__heading">
44+
What would you like to download?
45+
</h1>
46+
</legend>
47+
<div class="nhsuk-hint" id="download-hint">
48+
Select one option
49+
</div>
50+
<div class="nhsuk-radios nhsuk-radios--conditional">
51+
<div class="nhsuk-radios__item">
52+
<input class="nhsuk-radios__input" id="download-option-2" type="radio" name="download-option" value="1" aria-describedby="download-option-1-hint" aria-controls="conditional-download-1" aria-expanded="false">
53+
<label class="nhsuk-label nhsuk-radios__label" for="download-option-1">
54+
Download a blank template for bulk adding competencies to your framework
55+
</label>
56+
</div>
57+
<div class="nhsuk-radios__conditional nhsuk-radios__conditional--hidden" id="conditional-download-1">
58+
<div class="nhsuk-hint nhsuk-radios__hint" id="download-option-1-hint">
59+
This Excel file will be empty.<br />
60+
New competencies can be added by including their details on a blank row.
61+
</div>
62+
<a class="nhsuk-button nhsuk-button--secondary" role="button" asp-route-DownloadOption="1" asp-controller="FrameworksController" asp-action="DownloadCompetencies" target="_blank">
63+
Download template
64+
</a>
65+
</div>
66+
<div class="nhsuk-radios__item">
67+
<input class="nhsuk-radios__input" id="download-option-2" type="radio" name="download-option" value="2" aria-describedby="download-option-2-hint" aria-controls="conditional-download-2" aria-expanded="false">
68+
<label class="nhsuk-label nhsuk-radios__label" for="download-option-2">
69+
Download existing competencies in your framework for bulk modification and addition.
70+
</label>
71+
</div>
72+
<div class="nhsuk-radios__conditional nhsuk-radios__conditional--hidden" id="conditional-download-2">
73+
<div class="nhsuk-hint nhsuk-radios__hint" id="download-option-2-hint">
74+
This Excel file will include all existing competencies whose details you can update.<br />
75+
New competencies can be added by including their details on a blank row.
76+
</div>
77+
<a class="nhsuk-button nhsuk-button--secondary" role="button" asp-route-DownloadOption="2" asp-controller="FrameworksController" asp-action="DownloadCompetencies" target="_blank">
78+
Download competencies
79+
</a>
80+
</div>
81+
</div>
82+
</fieldset>
83+
84+
</div>
85+
</form>
86+
<h2>Upload file</h2>
87+
<p class="nhsuk-body-m">
88+
Once you have an Excel competencies workbook, add or update competencies to the worksheet, save and start the upload process.
89+
</p>
90+
<form class="nhsuk-u-margin-bottom-3" method="post" enctype="multipart/form-data">
91+
<input type="hidden" asp-for="@Model.FrameworkId" />
92+
<vc:file-input asp-for="@nameof(Model.ImportFile)" label="Excel file ready for import" hint-text="" css-class="nhsuk-u-width-one-half" />
93+
<button class="nhsuk-button" type="submit">Start upload</button>
94+
</form>
95+
<vc:cancel-link asp-controller="Frameworks" asp-action="ViewFramework" asp-all-route-data="@cancelLinkData" link-text="Cancel" />
96+
</div>
6097
</div>

0 commit comments

Comments
 (0)