From be0609d0f15fe25de404f2a1974a92ebc9423160 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Thu, 24 Oct 2024 10:53:18 +0100 Subject: [PATCH] Add pagination to Manage users and Vaccine batches (#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a pagination component to the bottom of the Manage users page, rather than just displaying all users on a single page. The number of users per page is set at 20, although this could be tweaked. The pagination component is not yet part of the NHS design system, so I’ve borrowed the GOV.UK one and applied NHS styling. This is still work-in-progress, and I’m working on contributing this to the NHS design system here: https://github.com/nhsuk/nhsuk-frontend/pull/1026 ## Screenshots ### Manage users | First page | Middle page | Last page | | ------ | ------ | ------ | | ![manage-users-page-1](https://github.com/user-attachments/assets/79f2e200-f771-4327-bc60-e4e30f706d09) | ![manage-users-page-3](https://github.com/user-attachments/assets/635e8a69-72ab-4f93-8fba-623d484cb225) | ![manage-users-page-5](https://github.com/user-attachments/assets/3ba01ff2-3d6d-48be-be78-5e79ec588794) | ### Vaccine batches (on vaccine product page for a given site) ![pagination-of-vaccine-batches](https://github.com/user-attachments/assets/99772ecc-8a09-4300-935f-0c463f6ae7a2) If there are a lot of pages, then ellipsis would be used to show gaps in the sequence, so that the first page, last page, previous and next page are always included: Screenshot 2024-09-23 at 16 09 43 --- app.js | 1 + app/assets/sass/main.scss | 2 + app/components/pagination/README.md | 5 + app/components/pagination/_pagination.scss | 240 ++++++ app/components/pagination/macro.njk | 3 + app/components/pagination/template.njk | 106 +++ app/data/session-data-defaults.js | 847 ++++++++++++++++++++- app/routes/user-admin.js | 29 +- app/routes/vaccines.js | 28 +- app/views/user-admin/index.html | 25 +- app/views/vaccines/product-page.html | 24 +- gulpfile.js | 4 +- 12 files changed, 1306 insertions(+), 8 deletions(-) create mode 100644 app/components/pagination/README.md create mode 100644 app/components/pagination/_pagination.scss create mode 100644 app/components/pagination/macro.njk create mode 100644 app/components/pagination/template.njk diff --git a/app.js b/app.js index b7c6767..b8fb7d9 100755 --- a/app.js +++ b/app.js @@ -55,6 +55,7 @@ const appViews = [ path.join(__dirname, 'node_modules/nhsuk-frontend/packages/macros'), path.join(__dirname, 'docs/views/'), path.join(__dirname, 'lib/prototype-admin/'), + path.join(__dirname, 'app/components/'), ]; const nunjucksConfig = { diff --git a/app/assets/sass/main.scss b/app/assets/sass/main.scss index c9f996d..888d86a 100755 --- a/app/assets/sass/main.scss +++ b/app/assets/sass/main.scss @@ -28,6 +28,8 @@ $govuk-brand-colour: $nhsuk-link-color; @import 'components/table'; +@import '../../components/pagination/_pagination'; + .autocomplete__wrapper ul > li { margin-bottom: 0; } diff --git a/app/components/pagination/README.md b/app/components/pagination/README.md new file mode 100644 index 0000000..cddc2b4 --- /dev/null +++ b/app/components/pagination/README.md @@ -0,0 +1,5 @@ +# Pagination + +This is a work-in-progress modification of the Pagination component to support numbered pages. + +See https://github.com/nhsuk/nhsuk-frontend/pull/1026 diff --git a/app/components/pagination/_pagination.scss b/app/components/pagination/_pagination.scss new file mode 100644 index 0000000..5c1677e --- /dev/null +++ b/app/components/pagination/_pagination.scss @@ -0,0 +1,240 @@ +/* ========================================================================== + COMPONENTS / #PAGINATION + ========================================================================== */ + +/** + * 1. Padding to give the icon spacing. + * 2. Append the word 'page' after next and + * previous on print stylesheets to make it easier + * to understand in print context. + */ + +// Previous and next pages variant +.nhsuk-pagination { + @include nhsuk-responsive-margin(7, "top"); + @include nhsuk-responsive-margin(7, "bottom"); +} + +.nhsuk-pagination__list { + @include clearfix(); +} + +.nhsuk-pagination-item--previous { + float: left; + text-align: left; + width: 50%; + + .nhsuk-icon { + left: -6px; + } + + .nhsuk-pagination__title { + padding-left: nhsuk-spacing(5); /* [1] */ + } +} + +.nhsuk-pagination-item--next { + float: right; + text-align: right; + width: 50%; + + .nhsuk-icon { + right: -6px; + } + + .nhsuk-pagination__title { + padding-right: nhsuk-spacing(5); /* [1] */ + } +} + +.nhsuk-pagination__link { + display: block; + position: relative; + text-decoration: none; + width: 100%; + + @include mq($media-type: print) { + color: $color_nhsuk-black; + } + + .nhsuk-icon { + position: absolute; + top: -2px; + + @include mq($media-type: print) { + color: $color_nhsuk-black; + margin-top: 0; + } + } + + &:hover { + color: $nhsuk-link-hover-color; + + .nhsuk-icon { + fill: $nhsuk-link-hover-color; + } + + .nhsuk-pagination__page { + text-decoration: none; + } + } + + &:focus { + @include nhsuk-focused-text; + + .nhsuk-pagination__page { + text-decoration: none; + } + + &:visited, + &:hover, + &:active { + .nhsuk-icon { + fill: $nhsuk-focus-text-color; + } + } + } + + &:visited { + .nhsuk-icon { + fill: $nhsuk-link-visited-color; + } + + &:hover { + .nhsuk-icon { + fill: $nhsuk-link-hover-color; + } + } + + &:focus { + .nhsuk-icon { + fill: $nhsuk-focus-text-color; + } + } + } +} + +.nhsuk-pagination__title { + @include nhsuk-typography-responsive(24); + + display: block; + + @include mq($media-type: print) { + &:after { + content: " page"; /* [2] */ + } + } +} + +.nhsuk-pagination__page { + @include nhsuk-typography-responsive(16); + + display: block; + text-decoration: underline; +} + +// Numbered pages variant +.nhsuk-pagination--numbered { + @include clearfix(); +} + +// In a numbered list, the previous link, numbered page links, +// and next link should all line up +.nhsuk-pagination--numbered__prev, +.nhsuk-pagination--numbered__next, +.nhsuk-pagination--numbered__item { + @include nhsuk-font(19); + box-sizing: border-box; + position: relative; + min-width: 45px; + min-height: 45px; + padding: nhsuk-spacing(2) nhsuk-spacing(2); + text-align: center; + margin: 0; + float: left; +} + +.nhsuk-pagination--numbered__link:visited:hover, +.nhsuk-pagination--numbered__item a:hover { + color: $nhsuk-link-hover-color; +} + +.nhsuk-pagination--numbered__prev:hover, +.nhsuk-pagination--numbered__next:hover, +.nhsuk-pagination--numbered__item:hover { + background-color: $color_nhsuk-grey-4; + color: $color_nhsuk-blue; +} + +// Container for the list of numbered items +.nhsuk-pagination__list--numbered { + margin: 0; + padding: 0; + list-style: none; +} + +// Current number +.nhsuk-pagination--numbered__item--current, +.nhsuk-pagination--numbered__item--current:hover { + background-color: $color_nhsuk-blue; + font-weight: $nhsuk-font-bold; + + .nhsuk-pagination--numbered__link { + color: $color_nhsuk-white; + } + + .nhsuk-pagination--numbered__link:focus { + color: $color_nhsuk-black; + } +} + +.nhsuk-pagination--numbered__item--ellipses { + font-weight: $nhsuk-font-bold; + color: $nhsuk-secondary-text-color; + + &:hover { + background-color: transparent; + } +} + +.nhsuk-pagination--numbered__link { + min-width: nhsuk-spacing(3); + vertical-align: middle; + color: $color_nhsuk-blue; + + // Increase the touch area for the link to the parent element. + @media screen { + &::after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } + } +} + +.nhsuk-pagination--numbered .nhsuk-icon { + margin-top: 0; + margin-bottom: -12px; + fill: $color_nhsuk-blue; +} + +.nhsuk-pagination--numbered__link:visited .nhsuk-icon { + fill: $nhsuk-link-visited-color; +} + +.nhsuk-pagination--numbered__link:hover .nhsuk-icon { + fill: $nhsuk-link-hover-color; +} + +.nhsuk-pagination--numbered .nhsuk-icon__arrow-left { + margin-left: -13px; + margin-right: nhsuk-spacing(2); +} + +.nhsuk-pagination--numbered .nhsuk-icon__arrow-right { + margin-left: nhsuk-spacing(2); + margin-right: -13px; +} diff --git a/app/components/pagination/macro.njk b/app/components/pagination/macro.njk new file mode 100644 index 0000000..168fb80 --- /dev/null +++ b/app/components/pagination/macro.njk @@ -0,0 +1,3 @@ +{% macro appPagination(params) %} + {%- include './template.njk' -%} +{% endmacro %} diff --git a/app/components/pagination/template.njk b/app/components/pagination/template.njk new file mode 100644 index 0000000..dc4699b --- /dev/null +++ b/app/components/pagination/template.njk @@ -0,0 +1,106 @@ +{% from "../../../node_modules/nhsuk-frontend/packages/macros/attributes.njk" import nhsukAttributes %} + +{# There are 2 variants of this component, one for content pages which +has only previous and next links including the titles of those pages, +and one for navigating between pages of items, like search results. + +The numbered variable sets which variant is being used, based on the +presence of items (pages). #} +{% set numbered = true if params.items %} + +{# Arrow pointing left - used by both variants #} +{%- macro _arrowPrevious() -%} + +{%- endmacro -%} + +{# Arrow pointing right - used by both variants #} +{%- macro _arrowNext() -%} + +{%- endmacro -%} + +{# Numbered page - included as a link within a list item. #} +{%- macro _pageItem(item) -%} +
  • + {% if item.ellipsis %} + ⋯ + {% else %} + + {{ item.number }} + + {% endif %} +
  • +{%- endmacro -%} + +{# Link for the previous or next or next page, displayed at either end of +the numbered variant only, and including an arrow pointing left or right. #} +{%- macro _arrowLink(link, type = "next") %} + {% set arrowType = arrowPrevious if type == "prev" else arrowNext %} +
    + + {%- if type == "prev" -%} + {{ _arrowPrevious() }} + {%- endif -%} + {{ caller() | safe }} + {%- if type == "next" -%} + {{ _arrowNext() }} + {%- endif -%} + +
    +{% endmacro -%} + + diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index 0b11e1b..82a61bd 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -46,13 +46,208 @@ module.exports = { }, { id: "263474", - batchNumber: "92/6334", + batchNumber: "634/6334", expiryDate: "2024-11-13" }, { id: "1367231", - batchNumber: "9282/4457", + batchNumber: "745/733", expiryDate: "2023-10-13" + }, + { + id: "25325", + batchNumber: "6634/336", + expiryDate: "2024-12-13" + }, + { + id: "1253252", + batchNumber: "13/6334", + expiryDate: "2024-11-13" + }, + { + id: "563463", + batchNumber: "34/324", + expiryDate: "2023-10-13" + }, + { + id: "127845", + batchNumber: "664/336", + expiryDate: "2024-12-23" + }, + { + id: "4025811", + batchNumber: "3525/6334", + expiryDate: "2023-11-13" + }, + { + id: "536325", + batchNumber: "535/242", + expiryDate: "2023-10-13" + }, + { + id: "141424", + batchNumber: "6443/336", + expiryDate: "2024-12-21" + }, + { + id: "64634", + batchNumber: "5233/6334", + expiryDate: "2024-10-21" + }, + { + id: "14235", + batchNumber: "252/134", + expiryDate: "2023-10-23" + }, + { + id: "25325", + batchNumber: "5235/336", + expiryDate: "2024-12-03" + }, + { + id: "73636", + batchNumber: "234/6334", + expiryDate: "2024-12-01" + }, + { + id: "85563", + batchNumber: "2535/7343", + expiryDate: "2024-12-19" + }, + { + id: "935346", + batchNumber: "525/336", + expiryDate: "2025-11-12" + }, + { + id: "527722", + batchNumber: "858/6334", + expiryDate: "2025-05-12" + }, + { + id: "633373", + batchNumber: "1424/131", + expiryDate: "2024-11-12" + }, + { + id: "4623442", + batchNumber: "424/336", + expiryDate: "2024-10-11" + }, + { + id: "745244", + batchNumber: "5235/6334", + expiryDate: "2024-11-27" + }, + { + id: "73343", + batchNumber: "2525/4457", + expiryDate: "2023-10-12" + }, + { + id: "1562", + batchNumber: "745/133", + expiryDate: "2023-10-29" + }, + { + id: "1322", + batchNumber: "6634/7455", + expiryDate: "2024-12-28" + }, + { + id: "62345", + batchNumber: "13/6234", + expiryDate: "2024-11-26" + }, + { + id: "25523", + batchNumber: "34/623", + expiryDate: "2023-10-25" + }, + { + id: "64343", + batchNumber: "664/624", + expiryDate: "2024-12-24" + }, + { + id: "35325", + batchNumber: "3525/413", + expiryDate: "2023-11-22" + }, + { + id: "73434", + batchNumber: "535/2462", + expiryDate: "2023-10-21" + }, + { + id: "53252", + batchNumber: "6443/562", + expiryDate: "2024-12-19" + }, + { + id: "74543", + batchNumber: "5233/5233", + expiryDate: "2024-10-18" + }, + { + id: "2486235", + batchNumber: "252/7434", + expiryDate: "2023-10-16" + }, + { + id: "5235", + batchNumber: "5235/743", + expiryDate: "2024-12-14" + }, + { + id: "523", + batchNumber: "234/244", + expiryDate: "2024-12-12" + }, + { + id: "52335", + batchNumber: "2535/8273", + expiryDate: "2024-12-11" + }, + { + id: "6323", + batchNumber: "525/623", + expiryDate: "2025-11-10" + }, + { + id: "27223", + batchNumber: "858/6233", + expiryDate: "2025-05-09" + }, + { + id: "52352", + batchNumber: "1424/3723", + expiryDate: "2024-11-06" + }, + { + id: "25373", + batchNumber: "424/344", + expiryDate: "2024-10-05" + }, + { + id: "5525235", + batchNumber: "5235/272", + expiryDate: "2024-11-02" + }, + { + id: "6747", + batchNumber: "2525/6346", + expiryDate: "2023-10-14" + }, + { + id: "25235", + batchNumber: "233/255", + expiryDate: "2024-10-02" + }, + { + id: "636346", + batchNumber: "16364/523", + expiryDate: "2025-02-04" } ] }, @@ -174,6 +369,654 @@ module.exports = { status: "Deactivated", deactivatedDate: "2024-03-09", clinician: "no" + }, + { + id: "248691", + email: "lilyana.marshall@nhs.net", + firstName: "Lilyana", + lastName: "Marshall", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "987459", + email: "rhys.mckenzie@nhs.net", + firstName: "Rhys", + lastName: "Mckenzie", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "7466454", + email: "joaquin.leblanc@nhs.net", + firstName: "Joaquin", + lastName: "Leblanc", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3363733", + email: "gisselle.stevens@nhs.net", + firstName: "Gisselle", + lastName: "Stevens", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "5640871", + email: "kane.mcdaniel@nhs.net", + firstName: "Kane", + lastName: "Mcdaniel", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "7076398", + email: "samuel.bray@nhs.net", + firstName: "Samuel", + lastName: "Bray", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "1006456", + email: "thomas.lucero@nhs.net", + firstName: "Thomas", + lastName: "Lucero", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "9335579", + email: "jaden.dennis@nhs.net", + firstName: "Jaden", + lastName: "Dennis", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2510011", + email: "tiana.peck@nhs.net", + firstName: "Tiana", + lastName: "Peck", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3039766", + email: "lorena.fox@nhs.net", + firstName: "Lorena", + lastName: "Fox", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "5407821", + email: "conner.osborn@nhs.net", + firstName: "Conner", + lastName: "Osborn", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "5941527", + email: "selena.warner@nhs.net", + firstName: "Selena", + lastName: "Warner", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "970879", + email: "aurora.huffman@nhs.net", + firstName: "Aurora", + lastName: "Huffman", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3232663", + email: "deandre.perkins@nhs.net", + firstName: "Deandre", + lastName: "Perkins", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "6665157", + email: "lily.holt@nhs.net", + firstName: "Lily", + lastName: "Holt", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2039091", + email: "angel.alvarado@nhs.net", + firstName: "Angel", + lastName: "Alvarado", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "657695", + email: "crystal.vega@nhs.net", + firstName: "Crystal", + lastName: "Vega", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "8320483", + email: "amiah.alvarado@nhs.net", + firstName: "Amiah", + lastName: "Alvarado", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "6570849", + email: "carlo.norman@nhs.net", + firstName: "Carlo", + lastName: "Norman", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "303510", + email: "jaelynn.chase@nhs.net", + firstName: "Jaelynn", + lastName: "Chase", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "7023266", + email: "heaven.mathews@nhs.net", + firstName: "Heaven", + lastName: "Mathews", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2374045", + email: "coleman.matthews@nhs.net", + firstName: "Coleman", + lastName: "Matthews", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "585216", + email: "june.stout@nhs.net", + firstName: "June", + lastName: "Stout", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "7124279", + email: "hayley.lee..@nhs.net", + firstName: "Hayley", + lastName: "Lee", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2984740", + email: "stephanie.meyer@nhs.net", + firstName: "Stephanie", + lastName: "Meyer", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2437147", + email: "cynthia.hart@nhs.net", + firstName: "Cynthia", + lastName: "Hart", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "869248", + email: "liliana.jacobson@nhs.net", + firstName: "Liliana", + lastName: "Jacobson", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3158509", + email: "pierce.barr@nhs.net", + firstName: "Pierce", + lastName: "Barr", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "7550048", + email: "collin.ewing@nhs.net", + firstName: "Collin", + lastName: "Ewing", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "6205997", + email: "esperanza.lyons@nhs.net", + firstName: "Esperanza", + lastName: "Lyons", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3646893", + email: "giovanni.tanner@nhs.net", + firstName: "Giovanni", + lastName: "Tanner", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "5589302", + email: "britney.joyce@nhs.net", + firstName: "Britney", + lastName: "Joyce", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "8404035", + email: "juliana.mathews@nhs.net", + firstName: "Juliana", + lastName: "Mathews", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "7814712", + email: "koen.stafford@nhs.net", + firstName: "Koen", + lastName: "Stafford", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "5066929", + email: "myles.mcguire@nhs.net", + firstName: "Myles", + lastName: "Mcguire", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2063882", + email: "evie.elliott@nhs.net", + firstName: "Evie", + lastName: "Elliott", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "752342", + email: "zara.pitts@nhs.net", + firstName: "Zara", + lastName: "Pitts", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "824220", + email: "daniel.lamb@nhs.net", + firstName: "Daniel", + lastName: "Lamb", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3057334", + email: "mohammed.burns@nhs.net", + firstName: "Mohammed", + lastName: "Burns", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2844017", + email: "kaylynn.rose@nhs.net", + firstName: "Kaylynn", + lastName: "Rose", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "6261906", + email: "tony.davenport@nhs.net", + firstName: "Tony", + lastName: "Davenport", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "1240005", + email: "darian.mcdonald@nhs.net", + firstName: "Darian", + lastName: "Mcdonald", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3495796", + email: "ryder.nicholson@nhs.net", + firstName: "Ryder", + lastName: "Nicholson", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "9430941", + email: "joyce.glover@nhs.net", + firstName: "Joyce", + lastName: "Glover", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3994171", + email: "uriel.rodgers@nhs.net", + firstName: "Uriel", + lastName: "Rodgers", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "4710304", + email: "liam.thornton@nhs.net", + firstName: "Liam", + lastName: "Thornton", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2699606", + email: "amber.roth@nhs.net", + firstName: "Amber", + lastName: "Roth", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "5521116", + email: "harry.garrison@nhs.net", + firstName: "Harry", + lastName: "Garrison", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "5903436", + email: "king.yates@nhs.net", + firstName: "King", + lastName: "Yates", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2916343", + email: "clayton.warner@nhs.net", + firstName: "Clayton", + lastName: "Warner", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "7737858", + email: "carley.ward@nhs.net", + firstName: "Carley", + lastName: "Ward", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "9166645", + email: "kristen.riley@nhs.net", + firstName: "Kristen", + lastName: "Riley", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "4566130", + email: "dominique.potts@nhs.net", + firstName: "Dominique", + lastName: "Potts", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "4427753", + email: "marisol.hatfield@nhs.net", + firstName: "Marisol", + lastName: "Hatfield", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "4758706", + email: "lesly.nolan@nhs.net", + firstName: "Lesly", + lastName: "Nolan", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "6055459", + email: "ricardo.knight@nhs.net", + firstName: "Ricardo", + lastName: "Knight", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "881056", + email: "alissa.wells@nhs.net", + firstName: "Alissa", + lastName: "Wells", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "1310503", + email: "mara.bryan@nhs.net", + firstName: "Mara", + lastName: "Bryan", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "9890694", + email: "jayleen.robertson@nhs.net", + firstName: "Jayleen", + lastName: "Robertson", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3048215", + email: "aron.delgado@nhs.net", + firstName: "Aron", + lastName: "Delgado", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2029291", + email: "abbigail.kirby@nhs.net", + firstName: "Abbigail", + lastName: "Kirby", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "5657137", + email: "estrella.villa@nhs.net", + firstName: "Estrella", + lastName: "Villa", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "7199138", + email: "aaden.shah@nhs.net", + firstName: "Aaden", + lastName: "Shah", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "1237996", + email: "gaven.davenport@nhs.net", + firstName: "Gaven", + lastName: "Davenport", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2087409", + email: "santos.goodman@nhs.net", + firstName: "Santos", + lastName: "Goodman", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "1069469", + email: "jeffery.page@nhs.net", + firstName: "Jeffery", + lastName: "Page", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "6692769", + email: "nola.watkins@nhs.net", + firstName: "Nola", + lastName: "Watkins", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "2792521", + email: "luciano.lucero@nhs.net", + firstName: "Luciano", + lastName: "Lucero", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "9560869", + email: "payton.howard@nhs.net", + firstName: "Payton", + lastName: "Howard", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "820103", + email: "hayden.horn@nhs.net", + firstName: "Hayden", + lastName: "Horn", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "3490354", + email: "toby.scott@nhs.net", + firstName: "Toby", + lastName: "Scott", + role: "Recorder", + status: "Active", + clinician: "no" + }, + { + id: "8256717", + email: "olive.ferrell@nhs.net", + firstName: "Olive", + lastName: "Ferrell", + role: "Recorder", + status: "Active", + clinician: "no" } ], organisationsAdded: [ diff --git a/app/routes/user-admin.js b/app/routes/user-admin.js index ddb5d7c..d61aa33 100644 --- a/app/routes/user-admin.js +++ b/app/routes/user-admin.js @@ -2,13 +2,40 @@ module.exports = (router) => { router.get('/user-admin', (req, res) => { + const perPage = 20; // Max number of users to show per page + const page = parseInt(req.query.page) || 1 ; // Current page, default to 1 + const data = req.session.data; const statusesToInclude = ['Invited', 'Active']; - const users = data.users.filter((user) => statusesToInclude.includes(user.status)) + const allUsers = data.users + .filter((user) => statusesToInclude.includes(user.status)) + .sort((a, b) => { + const nameA = a.firstName.toUpperCase(); // ignore upper and lowercase + const nameB = b.firstName.toUpperCase(); // ignore upper and lowercase + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + return 0; + }) + + + const totalUsers = allUsers.length + + const indexStartFrom = (page - 1) * perPage + + const users = allUsers.slice(indexStartFrom, indexStartFrom + perPage) + const totalPages = Math.ceil(totalUsers / perPage) + const deactivatedUsers = data.users.filter((user) => user.status === 'Deactivated') res.render('user-admin/index',{ + totalUsers, + totalPages, + page, users, deactivatedUsers }) diff --git a/app/routes/vaccines.js b/app/routes/vaccines.js index 12a3ebf..05a3f56 100644 --- a/app/routes/vaccines.js +++ b/app/routes/vaccines.js @@ -88,16 +88,42 @@ module.exports = (router) => { // Viewing a vaccine product at a site router.get('/vaccines/:id', (req, res) => { const data = req.session.data + const perPage = 20; // Max number of users to show per page + const page = parseInt(req.query.page) || 1 ; // Current page, default to 1 + const vaccine = data.vaccines.find((vaccine) => vaccine.id === req.params.id) if (!vaccine) { res.redirect('/vaccines'); return } const site = data.sites[vaccine.siteCode] const today = new Date().toISOString().substring(0,10) + const allBatches = vaccine.batches.sort((a, b) => { + const expiryA = a.expiryDate + const expiryB = b.expiryDate + if (expiryA > expiryB) { + return -1; + } + if (expiryA < expiryB) { + return 1; + } + return 0; + }) + + const totalBatches = allBatches.length + const indexStartFrom = (page - 1) * perPage + const totalPages = Math.ceil(totalBatches / perPage) + + const batches = allBatches.slice(indexStartFrom, indexStartFrom + perPage) + + res.render('vaccines/product-page', { vaccine, + batches, site, - today + today, + totalPages, + totalBatches, + page }) }) diff --git a/app/views/user-admin/index.html b/app/views/user-admin/index.html index 6c11cf5..2cf0e34 100644 --- a/app/views/user-admin/index.html +++ b/app/views/user-admin/index.html @@ -4,6 +4,9 @@ Manage users and permissions {% endblock %} +{% from '../../components/pagination/macro.njk' import appPagination %} + + {% set currentSection = "manage-users" %} {% block content %} @@ -17,7 +20,7 @@

    Manage users

    }) }} - +
    UsersUsers
    @@ -58,6 +61,26 @@

    Manage users

    + {% set items = [] %} + + {% for i in range(1, totalPages + 1) -%} + {% set items = (items.push({ + number: i, + href: "/user-admin?page=" + i, + current: (i === page) + }), items) %} + {%- endfor %} + + + {% if totalPages > 0 %} + {{ appPagination({ + previousUrl: "/user-admin?page=" + (page - 1) if page != 1, + nextUrl: "/user-admin?page=" + (page + 1) if page != totalPages, + items: items + }) }} + {% endif %} + + {% if deactivatedUsers | length > 0 %}

    View {{ deactivatedUsers | length | plural ("deactivated user") }}

    {% endif %} diff --git a/app/views/vaccines/product-page.html b/app/views/vaccines/product-page.html index 3a8ebcd..567243f 100644 --- a/app/views/vaccines/product-page.html +++ b/app/views/vaccines/product-page.html @@ -6,6 +6,9 @@ {% set currentSection = "vaccines" %} +{% from '../../components/pagination/macro.njk' import appPagination %} + + {% block beforeContent %} {{ backLink({ href: "/vaccines/", @@ -85,7 +88,7 @@

    {{ vaccine.vaccineProduct }}

    - {% for batch in vaccine.batches %} + {% for batch in batches %} {{ batch.batchNumber }} @@ -123,6 +126,25 @@

    {{ vaccine.vaccineProduct }}

    + {% set items = [] %} + + {% for i in range(1, totalPages + 1) -%} + {% set items = (items.push({ + number: i, + href: "/vaccines/" + vaccine.id + "?page=" + i, + current: (i === page) + }), items) %} + {%- endfor %} + + + {% if totalPages > 0 %} + {{ appPagination({ + previousUrl: "/vaccines/" + vaccine.id + "?page=" + (page - 1) if page != 1, + nextUrl: "/vaccines/" + vaccine.id + "?page=" + (page + 1) if page != totalPages, + items: items + }) }} + {% endif %} + {% endblock %} diff --git a/gulpfile.js b/gulpfile.js index edee77c..d4fb860 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -24,7 +24,7 @@ sass.compiler = require('sass'); // Compile SASS to CSS function compileStyles() { return gulp - .src(['app/assets/sass/**/*.scss', 'docs/assets/sass/**/*.scss']) + .src(['app/assets/sass/**/*.scss', 'docs/assets/sass/**/*.scss', 'app/components/**/*.scss']) .pipe(sass()) .pipe(gulp.dest('public/css')) .on('error', (err) => { @@ -92,7 +92,7 @@ function startBrowserSync(done) { proxy: 'localhost:' + port, port: port + 1000, ui: false, - files: ['app/views/**/*.*', 'docs/views/**/*.*'], + files: ['app/views/**/*.*', 'docs/views/**/*.*', 'app/components/**/*.*'], ghostMode: false, open: false, notify: true,