diff --git a/app/assets/javascript/add-another.js b/app/assets/javascript/add-another.js index cfb86296..23844906 100644 --- a/app/assets/javascript/add-another.js +++ b/app/assets/javascript/add-another.js @@ -14,6 +14,7 @@ import { Component } from 'nhsuk-frontend' * - Add `data-add-another-item="N"` to each item section (where N is the item index: 1, 2, 3, etc.) * - Add `data-add-another-add` to the "Add another" button (hidden by default) * - Add `data-add-another-remove="N"` to the "Remove" button within each section (hidden by default) + * - Optionally add `data-add-another-min="0"` to allow starting with no items visible (default is 1) * * @augments Component */ @@ -29,11 +30,13 @@ export class AddAnother extends Component { this.$items = Array.from(this.$root.querySelectorAll('[data-add-another-item]')) this.$addButton = this.$root.querySelector('[data-add-another-add]') this.$addButtonWrapper = this.$addButton?.closest('.nhsuk-button-group') + this.minItems = parseInt(this.$root.dataset.addAnotherMin ?? '1', 10) this.initializeItemVisibility() this.setupAddButton() this.setupRemoveButtons() this.updateAddButtonVisibility() + this.updateAddButtonText() this.updateRemoveButtonVisibility() } @@ -71,17 +74,18 @@ export class AddAnother extends Component { */ initializeItemVisibility() { // Find the last item with values - let lastFilledIndex = 0 + let lastFilledIndex = -1 this.$items.forEach(($item, index) => { if (this.hasInputValues($item)) { lastFilledIndex = index } }) - // Show items up to and including the last filled one (minimum 1) + // Show items up to and including the last filled one (respecting minItems) // Hide all items after that + const minVisibleIndex = this.minItems - 1 this.$items.forEach(($item, index) => { - if (index <= lastFilledIndex) { + if (index <= lastFilledIndex || index <= minVisibleIndex) { $item.hidden = false } else { $item.hidden = true @@ -152,6 +156,7 @@ export class AddAnother extends Component { } this.updateAddButtonVisibility() + this.updateAddButtonText() this.updateRemoveButtonVisibility() } @@ -163,8 +168,8 @@ export class AddAnother extends Component { removeItem(index) { const visibleItems = this.getVisibleItems() - // Don't remove if only one item is visible - if (visibleItems.length <= 1) { + // Don't remove if at minimum items + if (visibleItems.length <= this.minItems) { return } @@ -187,16 +192,34 @@ export class AddAnother extends Component { } this.updateAddButtonVisibility() + this.updateAddButtonText() this.updateRemoveButtonVisibility() } + /** + * Update the add button text based on number of visible items + * Uses data-add-another-text-first for the first item, + * data-add-another-text-another for subsequent items + */ + updateAddButtonText() { + if (!this.$addButton) return + + const firstText = this.$addButton.dataset.addAnotherTextFirst + const anotherText = this.$addButton.dataset.addAnotherTextAnother + + if (!firstText || !anotherText) return + + const visibleItems = this.getVisibleItems() + this.$addButton.textContent = visibleItems.length === 0 ? firstText : anotherText + } + /** * Update visibility of remove buttons based on number of visible items - * Remove buttons should only be visible when there are 2+ items + * Remove buttons should only be visible when there are more than minItems */ updateRemoveButtonVisibility() { const visibleItems = this.getVisibleItems() - const showRemoveButtons = visibleItems.length >= 2 + const showRemoveButtons = visibleItems.length > this.minItems this.$items.forEach($item => { const $removeButton = $item.querySelector('[data-add-another-remove]') diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index c0027bf0..e06dba75 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -15,8 +15,9 @@ module.exports = { vaccineStock: vaccineStock, lists: [], nhsNumberKnown: "yes", - currentUserId: "2387441662601", - currentOrganisationId: "RW3", + currentUserId: "6424325235325", + currentOrganisationId: null, + currentMode: "reports", vaccinationsRecorded: vaccinationsRecorded, // These are the options for extracting CSV reports diff --git a/app/routes.js b/app/routes.js index db5607e4..c794b938 100644 --- a/app/routes.js +++ b/app/routes.js @@ -51,6 +51,7 @@ require('./routes/user-profile')(router) require('./routes/vaccines')(router) require('./routes/reports')(router) require('./routes/records')(router) +require('./routes/pharmacies')(router) require('./routes/prototype-admin')(router) require('./routes/lists')(router) require('./routes/support')(router) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js new file mode 100644 index 00000000..8e0ecfc6 --- /dev/null +++ b/app/routes/pharmacies.js @@ -0,0 +1,110 @@ +const { getPharmaciesBelongingToOrganisation, getPharmacyChains, getOrganisation } = require('../lib/ods'); + +const sortByNameThenPostcode = (getPostcode = (item) => item.postcode) => (a, b) => { + if (a.name < b.name) return -1 + if (a.name > b.name) return 1 + const postcodeA = getPostcode(a) + const postcodeB = getPostcode(b) + if (postcodeA < postcodeB) return -1 + return 1 +} + +module.exports = router => { + + router.get('/pharmacies', (req, res) => { + const data = req.session.data + const currentUser = res.locals.currentUser + + const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) + + const organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) + + + res.render('pharmacies/index', { + organisations + }) + }) + + router.get('/pharmacies/select', async (req, res) => { + const data = req.session.data + const id = req.params.id + + let pharmacies = await getPharmaciesBelongingToOrganisation("P15J") + + pharmacies = pharmacies.sort(sortByNameThenPostcode((item) => item.address.postcode)) + + + res.render('pharmacies/select', { + pharmacies + }) + }) + + router.get('/pharmacies/check-selection', async (req, res) => { + const data = req.session.data + + let pharmacies = await getPharmaciesBelongingToOrganisation("P15J") + + pharmacies = pharmacies.filter((pharmacy) => { + return data.pharmacyIds.includes(pharmacy.id) + }).sort(sortByNameThenPostcode()) + + + res.render('pharmacies/check-selection', { + pharmacies + }) + }) + + router.get('/pharmacies/users',(req, res) => { + const data = req.session.data + const users = data.users.slice(10, 20) + + res.render('pharmacies/users/index', { + users + }) + }) + + router.get('/pharmacies/add-lead-admins',(req, res) => { + const data = req.session.data + const users = data.users.slice(10, 20) + + res.render('pharmacies/add-lead-admins', { + users + }) + }) + + router.get('/pharmacies/users/:id',(req, res) => { + const data = req.session.data + const id = req.params.id + const user = data.users.find((user) => user.id === id) + + res.render('pharmacies/users/user', { + user + }) + }) + + + router.get('/pharmacies/:id/add-users',(req, res) => { + const data = req.session.data + const users = data.users.slice(10, 20) + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + + res.render('pharmacies/add-users', { + users, + organisation + }) + }) + + router.get('/pharmacies/:id', (req, res) => { + const data = req.session.data + const id = req.params.id + + const organisation = data.organisations.find((organisation) => organisation.id === id) + + res.render('pharmacies/pharmacy', { + organisation + }) + }) + + +} diff --git a/app/views/home/index.html b/app/views/home/index.html index fad8e48b..a5a5e58f 100644 --- a/app/views/home/index.html +++ b/app/views/home/index.html @@ -13,7 +13,7 @@ {% include "includes/notification.html" %} -

{% if currentOrganisation %}{{ currentOrganisation.name }}{% else %}Overview{% endif %}

+

{% if currentOrganisation %}{{ currentOrganisation.name }}{% else %}PCT Healthcare{% endif %}

{% if currentOrganisation and totalVaccinationsRecorded == 0 %} diff --git a/app/views/includes/header.html b/app/views/includes/header.html index 7b92876d..58e9a25a 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -11,6 +11,21 @@ active: (currentSection == "home") }), navigationItems) %} + + {% if data.currentMode == "reports" %} + {% set navigationItems = (navigationItems.push({ + href: "/pharmacies", + text: "Pharmacies", + active: (currentSection == "pharmacies") + }), navigationItems) %} + + {% set navigationItems = (navigationItems.push({ + href: "/pharmacies/users", + text: "Manage users", + active: (currentSection == "pharmacies-users") + }), navigationItems) %} + {% endif %} + {% if currentOrganisation %} {% set navigationItems = (navigationItems.push({ href: "/record-vaccinations", diff --git a/app/views/pharmacies/add-lead-admins.html b/app/views/pharmacies/add-lead-admins.html new file mode 100644 index 00000000..c8e7da2c --- /dev/null +++ b/app/views/pharmacies/add-lead-admins.html @@ -0,0 +1,179 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Add lead administrators" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/check-selection" + }) }} +{% endblock %} + +{% block content %} +
+
+

{{ pageName }}

+ +
+ +

Who do you want to assign as lead administrators for all {{ data.pharmacyIds | length }} pharmacies?

+ + {% call details({ + summaryText: "What is a lead administrator?", + classes: "nhsuk-expander" + }) %} +

Lead administrators are responsible for setting up the pharmacy's users and vaccines.

+ +

They will get a Welcome email telling them how to log into RAVS. Once logged in at ABC pharmacy, they will be able to:

+ +
    +
  • add more users, with different permission levels
  • +
  • add vaccines
  • +
  • create reports
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Permission levelRecord and edit vaccinationsAdd and manage vaccinesCreate reportsAdd and manage users
Lead administratorYesYesYesYes
AdministratorYesYesYesNo
RecorderYesNoNoNo
+ {% endcall %} + + {% set items = [] %} + + {% set items = (items.push({ + text: "Me (" + currentUser.firstName + " " + currentUser.lastName + ")", + value: user.id, + hint: { + text: "Do not add yourself if you do not need to record vaccinations at these pharmacies" + } + }), items) %} + + {% for user in (users | sort(false, false, "firstName")) %} + {% set items = (items.push({ + text: user.firstName + " " + user.lastName, + value: user.id + }), items) %} + {% endfor %} + + {{ checkboxes({ + fieldset: { + legend: { + text: "Existing lead administrators", + size: "m" + } + }, + items: items + }) }} + +
+ + {# Ordinal suffixes for numbers 1-10 #} + {% set ordinals = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th'] %} + + {% for index in range(0, 10) %} +
+

{{ ordinals[index] }} new lead administrator

+ + + + {{ input({ + label: { + text: "First name" + }, + id: "first-name-" + (index + 1), + name: "firstName", + classes: "nhsuk-input--width-20", + value: data.firstName[index] + }) }} + + {{ input({ + label: { + text: "Last name" + }, + id: "last-name-" + (index + 1), + name: "lastName", + classes: "nhsuk-input--width-20", + value: data.lastName[index] + }) }} + + {{ input({ + label: { + text: "Email address" + }, + hint: { + html: "Use a personal nhs.net email, not a pharmacy email.
For example, joe.bloggs1@nhs.net" + }, + id: "email-" + (index + 1), + name: "email", + type: "email", + value: data.email[index] + }) }} +
+ {% endfor %} + + + + + {{ button({ + text: "Continue" + }) }} + + + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/add-users.html b/app/views/pharmacies/add-users.html new file mode 100644 index 00000000..09ae6b96 --- /dev/null +++ b/app/views/pharmacies/add-users.html @@ -0,0 +1,119 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Add user to " + organisation.name %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + organisation.id + }) }} +{% endblock %} + +{% block content %} +
+
+

{{ pageName }}

+ +

You can add an existing user to the pharmacy, or invite a new user.

+ +
+ + {% set items = [] %} + + {% for user in (users | sort(false, false, "firstName")) %} + {% set items = (items.push({ + text: user.firstName + " " + user.lastName, + value: user.id + }), items) %} + {% endfor %} + + {{ checkboxes({ + fieldset: { + legend: { + text: "Existing users", + size: "m" + } + }, + items: items + }) }} + +
+ + {# Ordinal suffixes for numbers 1-10 #} + + {% for index in range(0, 1) %} +
+

New user details

+ + + + {{ input({ + label: { + text: "First name" + }, + id: "first-name-" + (index + 1), + name: "firstName", + classes: "nhsuk-input--width-20", + value: data.firstName[index] + }) }} + + {{ input({ + label: { + text: "Last name" + }, + id: "last-name-" + (index + 1), + name: "lastName", + classes: "nhsuk-input--width-20", + value: data.lastName[index] + }) }} + + {{ input({ + label: { + text: "Email address" + }, + hint: { + html: "Use a personal nhs.net email, not a pharmacy email.
For example, joe.bloggs1@nhs.net" + }, + id: "email-" + (index + 1), + name: "email", + type: "email", + value: data.email[index] + }) }} +
+ {% endfor %} + + + + + {{ button({ + text: "Continue" + }) }} + + + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/check-selection.html b/app/views/pharmacies/check-selection.html new file mode 100644 index 00000000..9d99d841 --- /dev/null +++ b/app/views/pharmacies/check-selection.html @@ -0,0 +1,59 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Check your list of pharmacies" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/select" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ +

You have selected {{ pharmacies | length | plural("pharmacy") }}.

+ + + + + + + + + + + {% for pharmacy in pharmacies %} + + + + + {% endfor %} + + +
Pharmacy
{{ pharmacy.name }}, {{ pharmacy.address.postcode }} ({{ pharmacy.id }}) + {% if pharmacies | length > 1 %} +
+ + {{ button({ + text: "Remove", + classes: "nhsuk-button--small nhsuk-button--secondary nhsuk-u-margin-bottom-0" + }) }} +
+ {% endif %} +
+ +
+ {{ button({ + href: "/pharmacies/add-lead-admins", + text: "Continue" + }) }} +
+ +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html new file mode 100644 index 00000000..ecf76961 --- /dev/null +++ b/app/views/pharmacies/index.html @@ -0,0 +1,58 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% block content %} +
+
+ +

Pharmacies

+ +

Add new pharmacies or manage an existing pharmacy.

+ + {{ button({ + text: "Add pharmacies", + href: "/pharmacies/select" + }) }} + + + + + + + + + + + + + + {% for organisation in organisations %} + + + + + + + {% endfor %} + + +
Current pharmacies ({{ organisations | length }})
+ Name + + Vaccines + + Users +
+ {{ organisation.name }} ({{ organisation.id}}) + + COVID-19, flu + + 1 +
+
+
+ +{% endblock %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html new file mode 100644 index 00000000..1610e168 --- /dev/null +++ b/app/views/pharmacies/pharmacy.html @@ -0,0 +1,90 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% set pageName = organisation.name %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ + {{ summaryList({ + rows: [ + { + key: { + text: "ODS code" + }, + value: { + text: organisation.id + } + }, + { + key: { + text: "Address" + }, + value: { + text: organisation.address.line1 + ", " + organisation.address.town + ", " + organisation.address.postcode + } + } + ] + }) }} + +

Users

+ + {{ button({ + href: "/pharmacies/" + organisation.id + "/add-users", + text: "Add users" + }) }} + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Permission level + + Vaccinator + + Status +
+ Jane Smith + + Lead administrator + + Yes + + Active + +
+ +

Deactivate this pharmacy

+
+
+ +{% endblock %} diff --git a/app/views/pharmacies/select.html b/app/views/pharmacies/select.html new file mode 100644 index 00000000..11aa9abe --- /dev/null +++ b/app/views/pharmacies/select.html @@ -0,0 +1,194 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Add pharmacies" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies" + }) }} +{% endblock %} + +{% block content %} +
+
+
+ +

{{ pageName }}

+ +

There are {{ (pharmacies | length) + 7 }} active pharmacies in your company according to the NHS Organisation Data Service (ODS).

+ +

{{ (pharmacies | length) }} of them are not yet using RAVS and can be added below.

+ + + {% set items = [] %} + + {% for pharmacy in pharmacies %} + {% set items = (items.push({ + text: pharmacy.name + ", " + pharmacy.address.postcode + " (" + pharmacy.id + ")", + value: pharmacy.id + }), items) %} + {% endfor %} + + + {% call fieldset({ + legend: { + text: "Select pharmacies", + size: "m", + classes: "nhsuk-u-margin-bottom-5" + } + }) %} + + {% if (pharmacies | length) > 1 %} + {{ checkboxes({ + idPrefix: "pharmacy-select-all", + name: "pharmacyIds", + values: data.pharmacyIds, + formGroup: { + classes: "nhsuk-u-margin-bottom-2" + }, + items: [ + { + text: "Select all " + (pharmacies | length), + value: "", + attributes: { + "data-select-all": "true" + } + }, + { + divider: "or" + } + ] + }) }} + {% endif %} + + {% if (pharmacies | length) > 20 %} + {{ input({ + id: "pharmacy-search", + name: "pharmacySearch", + type: "search", + label: { + text: "Search" + }, + classes: "nhsuk-input--width-20", + attributes: { + "data-module": "app-filter-checkboxes" + }, + formGroup: { + classes: "nhsuk-u-margin-bottom-4" + } + }) }} + {% endif %} + + +
+ {{ checkboxes({ + id: "pharmacy-ids", + name: "pharmacyIds", + values: data.pharmacyIds, + items: items + }) }} +
+ + {% endcall %} + + + {{ button({ + text: "Continue" + }) }} + +
+
+
+ + + + +{% endblock %} diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html new file mode 100644 index 00000000..c7c66a3c --- /dev/null +++ b/app/views/pharmacies/users/index.html @@ -0,0 +1,48 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block content %} +
+
+ +

Users

+ +

Add a new user or manage an existing pharmacy.

+ + {{ button({ + text: "Add user", + href: "/pharmacies/users/new" + }) }} + + + + + + + + + + + + {% for user in users %} + + + + + {% endfor %} + + +
Current users
+ Name + + Email +
+ {{ user.firstName }} {{ user.lastName }} + + {{ user.email }} +
+
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html new file mode 100644 index 00000000..c6ebdf50 --- /dev/null +++ b/app/views/pharmacies/users/new.html @@ -0,0 +1,98 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

Add user

+ +
+ + {{ input({ + "label": { + "text": "First name" + }, + "id": "first-name", + "name": "firstName", + classes: "nhsuk-input--width-20", + value: data.firstName, + "errorMessage": { + "text": firstNameError + } if firstNameError + }) }} + + {{ input({ + "label": { + "text": "Last name" + }, + "id": "last-name", + "name": "lastName", + classes: "nhsuk-input--width-20", + value: data.lastName, + "errorMessage": { + "text": lastNameError + } if lastNameError + }) }} + + {{ input({ + "label": { + "text": "Email address", + size: "s" + }, + "id": "email", + "name": "email", + type: "email", + value: data.email + }) }} + + {{ checkboxes({ + idPrefix: "example", + name: "example", + fieldset: { + legend: { + text: "Which pharmacy do you want to add them to?", + size: "s", + isPageHeading: true + } + }, + items: [ + { + value: "all", + text: "All – add them as a super admin for Peak Pharmacy" + }, + { + divider: "or" + }, + { + text: "Peak Pharmacy (P141)" + }, + { + text: "Peak Pharmacy (P931)" + }, + { + text: "Peak Pharmacy (P291)" + }, + { + text: "etc" + } + ] + }) }} + + + {{ button({ + "text": "Continue" + }) }} +
+ +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html new file mode 100644 index 00000000..50bfb9a3 --- /dev/null +++ b/app/views/pharmacies/users/user.html @@ -0,0 +1,105 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = user.firstName + " " + user.lastName %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ + {{ summaryList({ + rows: [ + { + key: { + text: "Email" + }, + value: { + text: user.email + } + } + ] + }) }} + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Access and permission levels
+ Pharmacy + + Permission level + + Vaccinator + + Status +
+ Peak Pharmacy (P112) + + Lead administrator + + Yes + + Active + + Deactivate +
+ Peak Pharmacy (P9241) + + Recorder + + Yes + + Active + + Deactivate +
+ + {{ button({ + text: "Add to another pharmacy", + classes: "nhsuk-button--secondary" + }) }} + +

Deactivate user from all pharmacies

+ +
+
+ +{% endblock %}