diff --git a/app/views/_notification-user-admin.html b/_notification-user-admin.html similarity index 100% rename from app/views/_notification-user-admin.html rename to _notification-user-admin.html 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/assets/javascript/autocomplete.js b/app/assets/javascript/autocomplete.js index 695a7cf2..ec0c1a85 100644 --- a/app/assets/javascript/autocomplete.js +++ b/app/assets/javascript/autocomplete.js @@ -67,10 +67,6 @@ export class Autocomplete extends Component { /** * Selected option - * - * @param {*} value - Current value - * @param {Array} options - Available options - * @returns {HTMLOptionElement} Selected option */ selectedOption(value, options) { return [].filter.call( diff --git a/app/assets/javascript/checkbox-filter.js b/app/assets/javascript/checkbox-filter.js new file mode 100644 index 00000000..cec4561b --- /dev/null +++ b/app/assets/javascript/checkbox-filter.js @@ -0,0 +1,65 @@ +import { Component } from 'nhsuk-frontend' + +/** + * Checkbox Filter component + * + * Filters a list of checkboxes based on search input. + * + * Usage: + * - Add `data-module="app-checkbox-filter"` to a search input element + * - The component will filter `.nhsuk-checkboxes__item` elements within the same form + * - Checkboxes with `data-select-all` attribute are not filtered + * + * @augments Component + */ +export class CheckboxFilter extends Component { + static elementType = HTMLInputElement + + /** + * Name for the component used when initialising using data-module attributes + */ + static moduleName = 'app-checkbox-filter' + + /** + * @param {Element | null} $root - HTML input element to use for component + */ + constructor($root) { + super($root) + + this.$form = this.$root.closest('form') || this.$root.closest('fieldset') || document.body + this.$checkboxItems = this.$form.querySelectorAll('.nhsuk-checkboxes__item') + + this.$root.addEventListener('input', () => this.filter()) + } + + /** + * Filter checkbox items based on the search input value + */ + filter() { + const searchTerm = this.$root.value.toLowerCase().trim().replace(/[.()]/g, '') + const searchWords = searchTerm.split(/\s+/).filter(word => word.length > 0) + + this.$checkboxItems.forEach(($item) => { + const $label = $item.querySelector('.nhsuk-checkboxes__label') + const $checkbox = $item.querySelector('.nhsuk-checkboxes__input') + + if (!$label || !$checkbox) return + + // Skip if it's the select all checkbox + if ($checkbox.hasAttribute('data-select-all')) return + + const labelText = $label.textContent.toLowerCase().replace(/[.()]/g, '') + const labelWords = labelText.split(/\s+/) + const matches = searchWords.length === 0 || searchWords.every(searchWord => + labelWords.some(labelWord => labelWord.startsWith(searchWord)) + ) + + // Show only if matches search term + if (matches) { + $item.removeAttribute('hidden') + } else { + $item.setAttribute('hidden', '') + } + }) + } +} diff --git a/app/assets/javascript/checkbox-select-all.js b/app/assets/javascript/checkbox-select-all.js new file mode 100644 index 00000000..011be684 --- /dev/null +++ b/app/assets/javascript/checkbox-select-all.js @@ -0,0 +1,67 @@ +import { Component } from 'nhsuk-frontend' + +/** + * Checkbox Select All component + * + * Provides select all functionality for a group of checkboxes. + * + * Usage: + * - Add `data-module="app-checkbox-select-all"` to a checkbox input element + * - The component will control all other checkboxes in the same form/fieldset + * - The select-all checkbox shows indeterminate state when some (but not all) are checked + * + * @augments Component + */ +export class CheckboxSelectAll extends Component { + static elementType = HTMLInputElement + + /** + * Name for the component used when initialising using data-module attributes + */ + static moduleName = 'app-checkbox-select-all' + + /** + * @param {Element | null} $root - HTML input element to use for component + */ + constructor($root) { + super($root) + + this.$form = this.$root.closest('form') || this.$root.closest('fieldset') || document.body + + // All checkboxes except this one + this.$checkboxes = Array.from( + this.$form.querySelectorAll('input[type="checkbox"]') + ).filter(($checkbox) => $checkbox !== this.$root) + + // Click on select-all toggles all checkboxes + this.$root.addEventListener('change', () => this.toggleAll()) + + // Click on individual checkboxes updates select-all state + this.$checkboxes.forEach(($checkbox) => { + $checkbox.addEventListener('change', () => this.updateState()) + }) + + // Set initial state + this.updateState() + } + + /** + * Toggle all checkboxes to match the select-all checkbox state + */ + toggleAll() { + this.$checkboxes.forEach(($checkbox) => { + $checkbox.checked = this.$root.checked + }) + } + + /** + * Update the select-all checkbox state based on individual checkbox states + */ + updateState() { + const allChecked = this.$checkboxes.every(($checkbox) => $checkbox.checked) + const noneChecked = this.$checkboxes.every(($checkbox) => !$checkbox.checked) + + this.$root.checked = allChecked + this.$root.indeterminate = !allChecked && !noneChecked + } +} diff --git a/app/assets/javascript/checkbox-selected-count.js b/app/assets/javascript/checkbox-selected-count.js new file mode 100644 index 00000000..0827559c --- /dev/null +++ b/app/assets/javascript/checkbox-selected-count.js @@ -0,0 +1,109 @@ +import { ConfigurableComponent, I18n } from 'nhsuk-frontend' + +/** + * Checkbox Selected Count component + * + * Displays a count of selected checkboxes within a container with pluralization support. + * + * Usage: + * - Add `data-module="app-checkboxes-count-select"` to a wrapper element + * - Add an element with `data-selected-count-display` attribute to show the count + * - Configure i18n via data attributes on the module element: + * `data-i18n.selected-count.one="%{count} pharmacy selected"` + * `data-i18n.selected-count.other="%{count} pharmacies selected"` + * - Checkboxes with `data-select-all` attribute are excluded from the count + * + * @augments {ConfigurableComponent} + */ +export class CheckboxSelectedCount extends ConfigurableComponent { + static elementType = HTMLElement + + /** + * Name for the component used when initialising using data-module attributes + */ + static moduleName = 'app-checkboxes-count-select' + + /** + * @param {Element | null} $root - HTML element to use for component + * @param {Partial} [config] - Component config + */ + constructor($root, config = {}) { + super($root, config) + + this.$countDisplay = this.$root.querySelector('[data-selected-count-display]') + this.$checkboxes = this.$root.querySelectorAll('input[type="checkbox"]') + + this.i18n = new I18n(this.config.i18n) + + if (this.$countDisplay) { + this.setupEventListeners() + this.updateCount() + } + } + + /** + * Set up change event listeners on all checkboxes + */ + setupEventListeners() { + this.$checkboxes.forEach(($checkbox) => { + $checkbox.addEventListener('change', () => this.updateCount()) + }) + } + + /** + * Update the count display with the number of selected checkboxes + */ + updateCount() { + const checkedCount = this.$root.querySelectorAll( + 'input[type="checkbox"]:checked:not([data-select-all])' + ).length + + this.$countDisplay.textContent = this.i18n.t('selectedCount', { count: checkedCount }) + } + + /** + * Checkbox Selected Count default config + * + * @constant + * @type {CheckboxSelectedCountConfig} + */ + static defaults = Object.freeze({ + i18n: { + selectedCount: { + one: '%{count} selected', + other: '%{count} selected' + } + } + }) + + /** + * Checkbox Selected Count config schema + * + * @constant + * @satisfies {Schema} + */ + static schema = Object.freeze({ + properties: { + i18n: { type: 'object' } + } + }) +} + +/** + * Checkbox Selected Count config + * + * @typedef {object} CheckboxSelectedCountConfig + * @property {CheckboxSelectedCountTranslations} [i18n] - Checkbox Selected Count translations + */ + +/** + * Checkbox Selected Count translations + * + * @typedef {object} CheckboxSelectedCountTranslations + * @property {TranslationPluralForms} [selectedCount] - Count of selected checkboxes + */ + +/** + * @import { TranslationPluralForms } from 'nhsuk-frontend' + * @import { Schema } from 'nhsuk-frontend/dist/nhsuk/common/configuration/index.mjs' + */ diff --git a/app/assets/javascript/main.js b/app/assets/javascript/main.js index d101693a..c7464192 100644 --- a/app/assets/javascript/main.js +++ b/app/assets/javascript/main.js @@ -5,11 +5,19 @@ import { import { AddAnother } from './add-another.js' import { Autocomplete } from './autocomplete.js' +import { CheckboxFilter } from './checkbox-filter.js' +import { CheckboxSelectAll } from './checkbox-select-all.js' +import { CheckboxSelectedCount } from './checkbox-selected-count.js' +import { RadiosFilter } from './radios-filter.js' // Initiate NHS.UK frontend components on page load document.addEventListener('DOMContentLoaded', () => { createAll(AddAnother) createAll(Autocomplete) + createAll(CheckboxFilter) + createAll(CheckboxSelectAll) + createAll(CheckboxSelectedCount) + createAll(RadiosFilter) }) diff --git a/app/assets/javascript/radios-filter.js b/app/assets/javascript/radios-filter.js new file mode 100644 index 00000000..b5540722 --- /dev/null +++ b/app/assets/javascript/radios-filter.js @@ -0,0 +1,71 @@ +import { Component } from 'nhsuk-frontend' + +/** + * Radios Filter component + * + * Filters a list of radio buttons based on search input. + * + * Usage: + * - Add `data-module="app-radios-filter"` to a search input element + * - The component will filter `.nhsuk-radios__item` elements within the same form or fieldset + * - Radio items with `data-no-filter` on their input are not filtered (e.g. "add new" options) + * + * @augments Component + */ +export class RadiosFilter extends Component { + static elementType = HTMLInputElement + + /** + * Name for the component used when initialising using data-module attributes + */ + static moduleName = 'app-radios-filter' + + /** + * @param {Element | null} $root - HTML input element to use for component + */ + constructor($root) { + super($root) + + this.$form = this.$root.closest('fieldset') || this.$root.closest('form') || document.body + this.$radioItems = this.$form.querySelectorAll('.nhsuk-radios__item') + + this.$root.addEventListener('input', () => this.filter()) + } + + /** + * Filter radio items based on the search input value + */ + filter() { + const searchTerm = this.$root.value.toLowerCase().trim() + const searchWords = searchTerm.split(/[\s.@()]+/).filter(word => word.length > 0) + + console.log('Filtering radios with search term:', searchTerm) + + console.log('Radio items:', this.$radioItems) + + this.$radioItems.forEach(($item) => { + const $label = $item.querySelector('.nhsuk-radios__label') + const $radio = $item.querySelector('.nhsuk-radios__input') + + if (!$label || !$radio) return + + // Skip items marked as excluded from filtering (e.g. "add new" options) + if ($radio.hasAttribute('data-no-filter')) return + + const $hint = $item.querySelector('.nhsuk-radios__hint') + const labelText = $label.textContent.toLowerCase() + const hintText = $hint ? $hint.textContent.toLowerCase() : '' + const combinedText = `${labelText} ${hintText}` + const combinedWords = combinedText.split(/[\s.@()]+/).filter(word => word.length > 0) + const matches = searchWords.length === 0 || searchWords.every(searchWord => + combinedWords.some(word => word.startsWith(searchWord)) + ) + + if (matches) { + $item.removeAttribute('hidden') + } else { + $item.setAttribute('hidden', '') + } + }) + } +} diff --git a/app/assets/sass/components/_scrollable-container.scss b/app/assets/sass/components/_scrollable-container.scss new file mode 100644 index 00000000..e62f6b59 --- /dev/null +++ b/app/assets/sass/components/_scrollable-container.scss @@ -0,0 +1,23 @@ +.app-scrollable-container { + max-height: 500px; + overflow-y: scroll; + margin-bottom: nhsuk-spacing(4); + + // Force scrollbar to always be visible on WebKit browsers (Chrome, Safari) + &::-webkit-scrollbar { + width: $nhsuk-border-width-inset-text; + } + + &::-webkit-scrollbar-track { + background: $nhsuk-border-colour; + } + + &::-webkit-scrollbar-thumb { + background: $nhsuk-input-border-colour; + border-radius: 4px; + } +} + +.app-scrollable-container--fixed-height { + min-height: 500px; +} diff --git a/app/assets/sass/main.scss b/app/assets/sass/main.scss index 5a8af5a1..7ea6cd22 100755 --- a/app/assets/sass/main.scss +++ b/app/assets/sass/main.scss @@ -24,6 +24,7 @@ @import 'components/tag'; @import 'components/numbered-heading'; @import 'components/inset-text'; +@import 'components/scrollable-container'; @import '../../components/secondary-navigation/_secondary-navigation'; @@ -31,11 +32,13 @@ // Add your custom CSS/Sass styles below... /////////////////////////////////////////// -// Ensure hidden attribute works on button groups (which have display: flex) -.nhsuk-button-group[hidden] { +// Ensure hidden attribute works on button groups and radios (which have display: flex) +.nhsuk-button-group[hidden], +.nhsuk-radios__item[hidden] { display: none; } + .nhsuk-header--left .nhsuk-header__navigation-list { justify-content: normal; } @@ -145,23 +148,3 @@ border-radius: 3px; .nhsuk-checkboxes__item[hidden] { display: none; } - -.app-checkboxes--scrollable-container { - max-height: 500px; - overflow-y: scroll; - margin-bottom: nhsuk-spacing(4); - - // Force scrollbar to always be visible on WebKit browsers (Chrome, Safari) - &::-webkit-scrollbar { - width: $nhsuk-border-width-inset-text; - } - - &::-webkit-scrollbar-track { - background: $nhsuk-border-colour; - } - - &::-webkit-scrollbar-thumb { - background: $nhsuk-input-border-colour; - border-radius: 4px; - } -} \ No newline at end of file diff --git a/app/data/organisations.js b/app/data/organisations.js index 0427c632..a7296645 100644 --- a/app/data/organisations.js +++ b/app/data/organisations.js @@ -8261,6 +8261,7 @@ module.exports = [ { id: 'FA424', name: 'Pickfords Pharmacy', + companyId: "P0191N", sites: [ { id: "FA424X", @@ -8285,6 +8286,7 @@ module.exports = [ { id: 'FA02S', name: 'Addlestone Pharmacy', + companyId: "P0191N", address: { line1: '92a Station Road', town: 'Addlestone', @@ -8316,6 +8318,7 @@ module.exports = [ { id: 'FVJ99', name: 'Pharmacy 4U', + companyId: "P0191N", sites: [ { id: "123535", @@ -8336,10 +8339,15 @@ module.exports = [ lastName: "Brown", email: "james.brown@nhs.net", phone: "01234 567890" - } + }, + vaccines: [ + {name: "COVID-19", status: "enabled"}, + {name: "flu", status: "enabled"} + ] }, { id: 'PDL93', + companyId: "P0191N", name: 'Silverfields Chemists', sites: [ { @@ -8484,9 +8492,22 @@ module.exports = [ postcode: "LS2 7UE" } }, + { + id: 'P15951', + name: 'MediCare Health Ltd', + address: { + line1: '28 High Street', + town: 'London', + postcode: 'N5 1PL' + }, + type: 'Pharmacy HQ', + status: 'Active', + region: "Y56" + }, { id: 'FX9141', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '28 High Street', town: 'London', @@ -8514,6 +8535,7 @@ module.exports = [ { id: 'FX4825', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '104 Bow Street', town: 'London', @@ -8541,6 +8563,7 @@ module.exports = [ { id: 'FX7314', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '99 Flowers Road', town: 'London', @@ -8568,6 +8591,7 @@ module.exports = [ { id: 'FX9151', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '12 Church Road', town: 'London', @@ -8595,6 +8619,7 @@ module.exports = [ { id: 'FQ2525', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '1 Granary Road', town: 'London', @@ -8622,6 +8647,7 @@ module.exports = [ { id: 'FW1941', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '8 Manchester Road', town: 'London', @@ -8649,6 +8675,7 @@ module.exports = [ { id: 'FP9824', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '12 John Robinson Road', town: 'London', @@ -8676,6 +8703,7 @@ module.exports = [ { id: 'FP1812', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '18 Church Road', town: 'London', @@ -8703,6 +8731,7 @@ module.exports = [ { id: "FA7K23", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "45 High Street", town: "Manchester", @@ -8730,6 +8759,7 @@ module.exports = [ { id: "FG2R56", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "78 Queen Street", town: "Birmingham", @@ -8757,6 +8787,7 @@ module.exports = [ { id: "FH9P12", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "23 Station Road", town: "Leeds", @@ -8784,6 +8815,7 @@ module.exports = [ { id: "FJ4M89", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "156 Market Street", town: "Liverpool", @@ -8811,6 +8843,7 @@ module.exports = [ { id: "FK5N34", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "89 Park Lane", town: "Bristol", @@ -8838,6 +8871,7 @@ module.exports = [ { id: "FL7Q67", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "34 Castle Street", town: "Edinburgh", @@ -8865,6 +8899,7 @@ module.exports = [ { id: "FM8R23", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "67 Main Street", town: "Glasgow", @@ -8892,6 +8927,7 @@ module.exports = [ { id: "FN9S45", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "112 Church Road", town: "Cardiff", @@ -8919,6 +8955,7 @@ module.exports = [ { id: "FP2T78", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "45 Bridge Street", town: "Newcastle", @@ -8946,6 +8983,7 @@ module.exports = [ { id: "FQ3U12", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "28 Victoria Road", town: "Sheffield", @@ -8973,6 +9011,7 @@ module.exports = [ { id: "FR4V56", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "91 Oxford Street", town: "Nottingham", @@ -9000,6 +9039,7 @@ module.exports = [ { id: "FS5W89", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "15 King Street", town: "Leicester", @@ -9027,6 +9067,7 @@ module.exports = [ { id: "FT6X34", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "203 London Road", town: "Southampton", @@ -9077,5 +9118,17 @@ module.exports = [ } } ] + }, + { + id: "P0191N", + name: 'P.W. Pharmacies Ltd', + address: { + line1: '12 High Road', + town: 'Manchester', + postcode: 'M7 1LP' + }, + type: 'Pharmacy HQ', + status: 'Active', + region: "Y56" } ] diff --git a/app/data/users.js b/app/data/users.js index f3b4d98c..7453694c 100644 --- a/app/data/users.js +++ b/app/data/users.js @@ -82,35 +82,17 @@ module.exports = [ "firstName": "Phoebe", "lastName": "Black" }, - // Paulina Sloan is a lead admin for - // a chain of pharmacies + // Paulina Sloan is a group admin for + // a chain of pharmacies called + // P.W. Pharmacies Ltd { "id": "9847489647892", "email": "paulina.sloan@nhs.net", "organisations": [ { - "id": "FA424", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FA02S", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FVJ99", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "PDL93", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false + "id": "P0191N", + "permissionLevel": "Group administrator", + "status": "Active" } ], "firstName": "Paulina", @@ -131,10 +113,9 @@ module.exports = [ "firstName": "Jeremy", "lastName": "Blue" }, - { "id": "64746353", - "email": "jeremy.blue@nhs.net", + "email": "joseph.blue@nhs.net", "organisations": [ { "id": "FA02S", @@ -143,12 +124,12 @@ module.exports = [ "vaccinator": true } ], - "firstName": "Jeremy", - "lastName": "Blue" + "firstName": "Joseph", + "lastName": "White" }, { "id": "46436436436", - "email": "jeremy.blue@nhs.net", + "email": "jason.green@nhs.net", "organisations": [ { "id": "FVJ99", @@ -157,12 +138,12 @@ module.exports = [ "vaccinator": true } ], - "firstName": "Jeremy", - "lastName": "Blue" + "firstName": "Jason", + "lastName": "Green" }, { "id": "646436311", - "email": "jeremy.blue@nhs.net", + "email": "samantha.black@nhs.net", "organisations": [ { "id": "PDL93", @@ -171,11 +152,11 @@ module.exports = [ "vaccinator": true } ], - "firstName": "Jeremy", - "lastName": "Blue" + "firstName": "Samantha", + "lastName": "Black" }, - // Amanda White is a lead admin for - // a chain of pharmacies + // Amanda White is a group administrator for the + // MediCare Health Ltd chain of pharmacies { "firstName": "Amanda", "lastName": "White", @@ -183,145 +164,32 @@ module.exports = [ "email": "amanda.white@nhs.net", "organisations": [ { - "id": "FX9141", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FX4825", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FX7314", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FX9151", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FQ2525", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FW1941", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FP9824", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FP1812", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FA7K23", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FG2R56", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FH9P12", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FA7K23", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FJ4M89", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FK5N34", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FL7Q67", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FM8R23", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FN9S45", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FP2T78", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FQ3U12", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FR4V56", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FS5W89", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, + "id": "P15951", + "permissionLevel": "Group administrator", + "status": "Active" + } + ] + }, + { + "id": "34634617277", + "email": "peter.orange@nhs.net", + "organisations": [ { - "id": "FT6X34", - "permissionLevel": "Lead administrator", + "id": "RCY", + "permissionLevel": "Recorder", "status": "Active", - "vaccinator": false + "vaccinator": true } - ] + ], + "firstName": "Peter", + "lastName": "Orange" }, { "id": "1394978032564", "email": "ocean.merritt@nhs.net", "organisations": [ { - "id": "RCY", + "id": "FR4V56", "permissionLevel": "Recorder", "status": "Active", "vaccinator": true diff --git a/app/filters.js b/app/filters.js index ce8b19fc..9d48df16 100644 --- a/app/filters.js +++ b/app/filters.js @@ -67,23 +67,6 @@ module.exports = function () { } } - /** - * Ensure a value is always returned as an array - * Useful for form fields with [] notation that may return a string if only one value - * - * @param {*} value - Value to convert to array - * @returns {Array} Value as an array - */ - filters.asArray = function(value) { - if (value === undefined || value === null) { - return [] - } - if (Array.isArray(value)) { - return value - } - return [value] - } - /* keep the following line to return your filters to the app */ return filters } diff --git a/app/lib/utils/by-name.js b/app/lib/utils/by-name.js new file mode 100644 index 00000000..e0fa4539 --- /dev/null +++ b/app/lib/utils/by-name.js @@ -0,0 +1,15 @@ +// This function can be used to sort a list of +// users by first name and then last name +const byName = function (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; +} + +module.exports.byName = byName 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/auth.js b/app/routes/auth.js index 7959d8fb..4390d5d5 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -21,10 +21,6 @@ module.exports = router => { .filter((organisation) => organisation.status === "Active") .map((organisation) => organisation.id) - const organisationsUserIsAnAdminAt = (user.organisations || []) - .filter((organisation) => (organisation.status === "Active" && ["Lead administrator", "Administrator"].includes(organisation.permissionLevel))) - .map((organisation) => organisation.id) - const userRegionIds = (user.regions || []) .filter((organisation) => organisation.status === "Active") .map((organisation) => organisation.id) @@ -49,11 +45,6 @@ module.exports = router => { res.redirect('/regions') - } else if (organisationsUserIsAnAdminAt.length > 1) { - // They are an admin at 2 or more organisations, so - // ask them to select mode (single org or report mode) - res.redirect('/auth/select-mode') - } else { res.redirect('/auth/select-organisation') @@ -62,29 +53,6 @@ module.exports = router => { }) - - router.post('/auth/answer-select-mode', (req, res) => { - const data = req.session.data - const loginMode = data.loginMode - - if (loginMode === 'single') { - res.redirect('/auth/select-organisation?from=select-mode') - } else if (loginMode === 'create-reports') { - - const email = data.email - const user = data.users.find((user) => user.email === email) - - req.session.data.currentMode = "reports" - req.session.data.currentOrganisationId = null - req.session.data.currentUserId = user.id - - res.redirect('/home') - } else { - res.redirect('/auth/select-mode') - } - - }) - router.get('/auth/select-organisation', (req, res) => { const data = req.session.data @@ -122,7 +90,6 @@ module.exports = router => { router.get('/sign-out', (req, res) => { req.session.data.currentUserId = null req.session.data.currentOrganisationId = null - req.session.data.currentMode = null req.session.data.email = "" res.redirect('/product-page') diff --git a/app/routes/home.js b/app/routes/home.js index bc890a75..7bee500f 100644 --- a/app/routes/home.js +++ b/app/routes/home.js @@ -64,7 +64,6 @@ module.exports = router => { // Dashboard router.get('/home', (req, res) => { const currentOrganisation = res.locals.currentOrganisation - const currentUser = res.locals.currentUser const data = req.session.data const allVaccinationsRecorded = data.vaccinationsRecorded @@ -73,39 +72,39 @@ module.exports = router => { // Vaccinations to count let vaccinationsRecorded = [] - let sites = [] - let organisations = [] + let pharmacies = [] if (currentOrganisation) { - // Showing all sites for now, for demo purposes - sites = currentOrganisation.sites - // Filter vaccinations to only those recorded by the current - // organisation - vaccinationsRecorded = allVaccinationsRecorded.filter((vaccination)=> vaccination.organisationId === currentOrganisation.id) + if (currentOrganisation.type == "Pharmacy HQ") { - if (!sites.length || sites.length === 0) { - sites = [currentOrganisation] - } + pharmacies = data.organisations.filter((organisation) => organisation.companyId === currentOrganisation.id) - } else { + vaccinationsRecorded = allVaccinationsRecorded - // Include all organisations for now - vaccinationsRecorded = allVaccinationsRecorded - const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) - organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) - } + } else { + + // Showing all sites for now, for demo purposes + sites = currentOrganisation.sites || [] + // Filter vaccinations to only those recorded by the current + // organisation + vaccinationsRecorded = allVaccinationsRecorded.filter((vaccination)=> vaccination.organisationId === currentOrganisation.id) + + if (!sites.length || sites.length === 0) { + sites = [currentOrganisation] + } + } + } let totalsBySite = [] - let totalsByOrganisation = [] + let totalsByPharmacy = [] let totalsByVaccine = [] let totalsByDay = [] - const totalVaccinationsRecorded = countVaccinations(vaccinationsRecorded) const totalVaccinationsRecordedToday = countVaccinations( @@ -190,28 +189,28 @@ module.exports = router => { } } - for (let organisation of organisations) { + for (let pharmacy of pharmacies) { const total = countVaccinations(vaccinationsRecorded, { - organisationId: organisation.id + organisationId: pharmacy.id }) if (total !== -1) { - totalsByOrganisation.push({ - organisationId: organisation.id, - organisationName: organisation.name, + totalsByPharmacy.push({ + organisationId: pharmacy.id, + organisationName: pharmacy.name, today: countVaccinations(vaccinationsRecorded, { date: dateToday, - organisationId: organisation.id + organisationId: pharmacy.id }), month:countVaccinations(vaccinationsRecorded, { month: dateToday, - organisationId: organisation.id + organisationId: pharmacy.id }), past7Days: countVaccinations(vaccinationsRecorded, { minDate: sevenDaysAgo, maxDate: dateToday, - organisationId: organisation.id + organisationId: pharmacy.id }), total: total }) @@ -220,6 +219,7 @@ module.exports = router => { res.render('home/index', { sites, + pharmacies, totalVaccinationsRecorded, totalVaccinationsRecordedToday, totalVaccinationsRecordedThisMonth, @@ -228,7 +228,7 @@ module.exports = router => { totalsBySite, totalsByVaccine, totalsByDay, - totalsByOrganisation + totalsByPharmacy }) }) } diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js new file mode 100644 index 00000000..be88e31d --- /dev/null +++ b/app/routes/pharmacies.js @@ -0,0 +1,561 @@ +const { getPharmaciesBelongingToOrganisation } = require('../lib/ods'); +const { byName } = require('../lib/utils/by-name'); + + +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 added = req.query.added + + const companyId = res.locals.currentOrganisation.id + + const organisations = data.organisations.filter((organisation) => organisation.companyId === companyId).sort(sortByNameThenPostcode()) + + let organisationUserCounts = {} + + for (const organisation of organisations) { + organisationUserCounts[organisation.id] = data.users + .filter((user) => (user.organisations || []) + .find((orgPermission) => orgPermission.id === organisation.id) + ).length + } + + + res.render('pharmacies/index', { + organisations, + organisationUserCounts, + added + }) + }) + + router.get('/pharmacies/select', async (req, res) => { + + 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 + }) + }) + + // Actually add the pharmacies + router.post('/pharmacies/added', async (req, res) => { + const data = req.session.data + + const companyId = res.locals.currentOrganisation.id + + let pharmacies = await getPharmaciesBelongingToOrganisation("P15J") + + pharmacies = pharmacies.filter((pharmacy) => { + return data.pharmacyIds.includes(pharmacy.id) + }).sort(sortByNameThenPostcode()) + + for (const pharmacy of pharmacies) { + + data.organisations.push({ + id: pharmacy.id, + name: pharmacy.name, + type: 'Community Pharmacy', + companyId: companyId, + address: pharmacy.address, + status: 'Active', + vaccines: [ + {name: 'flu', status: 'enabled'} + ], + sites: [ + { + id: pharmacy.id, + name: pharmacy.name + } + ] + }) + } + + res.redirect(`/pharmacies?added=${pharmacies.length}`) + }) + + router.get('/pharmacies/users',(req, res) => { + const data = req.session.data + const companyId = res.locals.currentOrganisation.id + const pharmacies = data.organisations.filter((organisation) => organisation.companyId === companyId) + + const pharmacyIds = pharmacies.map(pharmacy => pharmacy.id) + + let users = data.users.sort(byName) + + users = users.filter(function(user) { + // Get the IDs of all the organisations they have access to + const userOrganisationIds = (user.organisations || []).map((organisation) => organisation.id) + + // See whether any of those organisations are the pharmacies in + // this chain, or the head office company id + return userOrganisationIds.some(id => pharmacyIds.includes(id) || id === companyId) + }) + + const groupAdministrators = users.filter(function(user) { + return user.organisations.find(org => org.permissionLevel === "Group administrator") + }) + + // Filter out group admins from the general user list + users = users.filter((user) => !groupAdministrators.includes(user)) + + res.render('pharmacies/users/index', { + users, + groupAdministrators + }) + }) + + router.get('/pharmacies/users/new',(req, res) => { + + res.render('pharmacies/users/new') + }) + + router.post('/pharmacies/users/new-answer',(req, res) => { + const data = req.session.data + const groupAdministrator = data.groupAdministrator + + if (groupAdministrator === "yes") { + res.redirect('/pharmacies/users/check') + } else { + res.redirect('/pharmacies/users/new-select-pharmacies') + } + }) + + router.get('/pharmacies/users/new-select-pharmacies',(req, res) => { + const data = req.session.data + const companyId = res.locals.currentOrganisation.id + const pharmacies = data.organisations.filter((organisation) => organisation.companyId === companyId) + + res.render('pharmacies/users/new-select-pharmacies', { + pharmacies + }) + }) + + router.get('/pharmacies/users/new-select-pharmacies-check',(req, res) => { + const data = req.session.data + const pharmacyIds = data.pharmacyIds + + // Get pharmacies selected on previous page + const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) + + + res.render('pharmacies/users/new-select-pharmacies-check', { + pharmacies + }) + }) + + router.get('/pharmacies/users/new-permission-level',(req, res) => { + const data = req.session.data + const pharmacyIds = data.pharmacyIds || [] + + // Get pharmacies selected on previous page + const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) + + + res.render('pharmacies/users/new-permission-level', { + pharmacies + }) + }) + + 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/check',(req, res) => { + const data = req.session.data + const pharmacyIds = data.pharmacyIds || [] + + // Get pharmacies selected on previous page + const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) + + res.render('pharmacies/users/check', { + pharmacies + }) + + + }) + + router.post('/pharmacies/users/check-answer',(req, res) => { + const data = req.session.data + const groupAdministrator = data.groupAdministrator + const pharmacyIds = data.pharmacyIds || [] + + const user = { + id: Math.floor(Math.random() * 10000000).toString(), + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + organisations: [] + } + + if (groupAdministrator === "yes") { + user.organisations.push({ + id: res.locals.currentOrganisation.id, + status: 'Invited', + permissionLevel: "Group administrator" + }) + } + + if (pharmacyIds.length > 0) { + + for (const pharmacyId of pharmacyIds) { + + user.organisations.push({ + id: pharmacyId, + status: 'Invited', + permissionLevel: data.permissionLevel, + vaccinator: data.vaccinator + }) + } + + } + + data.users.push(user) + + // Reset answers + data.firstName = '' + data.lastName = '' + data.email = '' + data.permissionLevel = '' + + res.redirect('/pharmacies/users?added=true') + }) + + router.get('/pharmacies/:id/deactivate',(req, res) => { + const data = req.session.data + const id = req.params.id + const pharmacy = data.organisations.find(organisation => organisation.id === id) + + res.render('pharmacies/deactivate', { + pharmacy + }) + }) + + router.post('/pharmacies/:id/deactivate-answer',(req, res) => { + const data = req.session.data + const id = req.params.id + const pharmacy = data.organisations.find(organisation => organisation.id === id) + + pharmacy.status = 'Deactivated' + + res.redirect(`/pharmacies/${id}`) + }) + + router.get('/pharmacies/:id/add-user',(req, res) => { + const data = req.session.data + const users = data.users.slice(10, 30) + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + + res.render('pharmacies/add-user', { + users, + organisation + }) + }) + + router.get('/pharmacies/:id/add-user-permission-level',(req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + let existingUser + + if (data.userId) { + existingUser = data.users.find((user) => user.id === data.userId) + } + + res.render('pharmacies/add-user-permission-level', { + organisation, + existingUser + }) + }) + + router.get('/pharmacies/:id/add-user-check',(req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + let existingUser + + if (data.userId) { + existingUser = data.users.find((user) => user.id === data.userId) + } + + res.render('pharmacies/add-user-check', { + organisation, + existingUser + }) + }) + + router.get('/pharmacies/:id/user-added',(req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + const existingUser = data.users.find((user) => user.id === data.userId) + + if (existingUser) { + + existingUser.organisations ||= [] + existingUser.organisations.push({ + id: organisation.id, + status: 'Active', + vaccinator: (data.vaccinator === 'yes'), + permissionLevel: data.permissionLevel + }) + } else { + + data.users.push({ + id: Math.floor(Math.random() * 10000000).toString(), + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + organisations: [ + { + id: organisation.id, + status: 'Invited', + vaccinator: (data.vaccinator === 'yes'), + permissionLevel: data.permissionLevel + } + ] + }) + + } + + // Reset data + req.session.data.userId = '' + req.session.data.email = '' + req.session.data.firstName = '' + req.session.data.lastName = '' + req.session.data.permissionLevel = '' + req.session.data.vaccinator = '' + + res.redirect(`/pharmacies/${organisation.id}?added=true`) + }) + + + router.get('/pharmacies/:pharmacyId/users/:userId/change',(req, res) => { + const data = req.session.data + const pharmacyId = req.params.pharmacyId + const userId = req.params.userId + + const user = data.users.find(user => user.id === userId) + const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) + + const role = user.organisations.find(userOrg => userOrg.id === pharmacyId) + + res.render('pharmacies/users/change-user-role', { + user, + pharmacy, + role + }) + }) + + router.get('/pharmacies/:pharmacyId/users/:userId/deactivate-from-pharmacy',(req, res) => { + const data = req.session.data + const pharmacyId = req.params.pharmacyId + const userId = req.params.userId + + const user = data.users.find(user => user.id === userId) + const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) + + res.render('pharmacies/users/deactivate-from-pharmacy', { + user, + pharmacy + }) + }) + + router.post('/pharmacies/:pharmacyId/users/:userId/deactivate-from-pharmacy-answer',(req, res) => { + const data = req.session.data + const pharmacyId = req.params.pharmacyId + const userId = req.params.userId + + const user = data.users.find(user => user.id === userId) + const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) + + const role = user.organisations.find(role => role.id === pharmacyId) + + role.status = 'Deactivated' + + res.redirect(`/pharmacies/users/${user.id}?deactivatedFromPharmacyId=${pharmacy.id}`) + + }) + + + router.post('/pharmacies/:pharmacyId/users/:userId/change-answer',(req, res) => { + const data = req.session.data + const pharmacyId = req.params.pharmacyId + const userId = req.params.userId + const from = data.from + + const user = data.users.find(user => user.id === userId) + const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) + + const role = user.organisations.find(userOrg => userOrg.id === pharmacyId) + + role.permissionLevel = data.permissionLevel + role.vaccinator = (data.vaccinator === "yes") + + if (from === "user") { + res.redirect(`/pharmacies/users/${user.id}`) + } else { + res.redirect(`/pharmacies/${pharmacy.id}`) + } + + }) + + router.get('/pharmacies/users/:id/add-to',(req, res) => { + const data = req.session.data + const userId = req.params.id + const user = data.users.find(user => user.id === userId) + const companyId = res.locals.currentOrganisation.id + + const pharmacyIdsThatUserAlreadyHasAccessTo = user.organisations.map(organisation => organisation.id) + + const pharmacies = data.organisations.filter((organisation) => (organisation.companyId === companyId) && !pharmacyIdsThatUserAlreadyHasAccessTo.includes(organisation.id)) + + + res.render('pharmacies/users/add-to', { + user, + pharmacies + }) + }) + + router.get('/pharmacies/users/:id/add-to-permission-level',(req, res) => { + const data = req.session.data + const userId = req.params.id + const user = data.users.find(user => user.id === userId) + + const pharmacy = data.organisations.find(organisation => organisation.id === data.pharmacyId) + + + res.render('pharmacies/users/add-to-permission-level', { + user, + pharmacy + }) + }) + + router.get('/pharmacies/users/:id/add-to-check',(req, res) => { + const data = req.session.data + const userId = req.params.id + const user = data.users.find(user => user.id === userId) + + const pharmacy = data.organisations.find(organisation => organisation.id === data.pharmacyId) + + + res.render('pharmacies/users/add-to-check', { + user, + pharmacy + }) + }) + + router.post('/pharmacies/users/:id/add-to-check-answer',(req, res) => { + const data = req.session.data + const id = req.params.id + const user = data.users.find(user => user.id === id) + const pharmacy = data.organisations.find(organisation => organisation.id === data.pharmacyId) + + user.organisations.push({ + id: pharmacy.id, + status: 'Active', + permissionLevel: data.permissionLevel, + vaccinator: (data.vaccinator === 'yes') + }) + + // Reset answers + data.permissionLevel = '' + data.vaccinator = '' + data.pharmacyId = '' + + res.redirect(`/pharmacies/users/${id}?addedToPharmacyId=${pharmacy.id}`) + }) + + + router.get('/pharmacies/:id', (req, res) => { + const data = req.session.data + const id = req.params.id + const added = req.query.added + + + const organisation = data.organisations.find((organisation) => organisation.id === id) + + const userOrganisationPermissions = {} + + const users = data.users + .filter((user) => (user.organisations || []) + .find((orgPermission) => orgPermission.id === organisation.id) + ) + + for (const user of users) { + userOrganisationPermissions[user.id] = user.organisations.find((userOrganisation) => userOrganisation.id === organisation.id) + } + + res.render('pharmacies/pharmacy', { + organisation, + users, + userOrganisationPermissions, + added + }) + }) + + + 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) + const companyId = res.locals.currentOrganisation.id + + const addedToPharmacyId = req.query.addedToPharmacyId + const deactivatedFromPharmacyId = req.query.deactivatedFromPharmacyId + + let addedToPharmacy, deactivatedFromPharmacy + + if (addedToPharmacyId) { + addedToPharmacy = data.organisations.find(organisation => organisation.id === addedToPharmacyId) + } + if (deactivatedFromPharmacyId) { + deactivatedFromPharmacy = data.organisations.find(organisation => organisation.id === deactivatedFromPharmacyId) + } + + const totalPharmaciesAtOrganisation = data.organisations.filter(organisation => organisation.companyId === companyId).length + + const pharmacyRoles = (user.organisations || []).filter(role => role.permissionLevel !== "Group administrator") + + res.render('pharmacies/users/user', { + user, + pharmacyRoles, + addedToPharmacy, + deactivatedFromPharmacy, + totalPharmaciesAtOrganisation + }) + }) + +} diff --git a/app/routes/reports.js b/app/routes/reports.js index 234edeaa..230ac52a 100644 --- a/app/routes/reports.js +++ b/app/routes/reports.js @@ -7,13 +7,15 @@ module.exports = (router) => { const currentOrganisation = res.locals.currentOrganisation let vaccinationsRecordedCount - if (currentOrganisation) { - vaccinationsRecordedCount = data.vaccinationsRecorded.filter((vaccination) => vaccination.organisationId === currentOrganisation.id).length + if (currentOrganisation.type === "Pharmacy HQ") { + + // TODO count vaccinations recorded at any pharmacies within this group. + vaccinationsRecordedCount = 1 + } else { - // TODO: count across all organisations you - // have access to - vaccinationsRecordedCount = 100 + vaccinationsRecordedCount = data.vaccinationsRecorded.filter((vaccination) => vaccination.organisationId === currentOrganisation.id).length + } res.render('reports/index', { @@ -43,6 +45,16 @@ module.exports = (router) => { }) }) + router.post('/reports/choose-vaccines-answer', (req, res) => { + + if (res.locals.currentOrganisation.type === "Pharmacy HQ") { + res.redirect('/reports/choose-pharmacies') + } else { + res.redirect('/reports/choose-site') + } + + }) + router.get('/reports/choose-dates', (req, res) => { const data = req.session.data @@ -94,30 +106,24 @@ module.exports = (router) => { }) router.get('/reports/choose-site', (req, res) => { - const data = req.session.data const currentOrganisation = res.locals.currentOrganisation - const currentUser = res.locals.currentUser - let sites, organisations - - if (currentOrganisation) { - // Showing all sites for now, for demo purposes - sites = currentOrganisation.sites - - if (sites === []) { - sites = [currentOrganisation] - } + const sites = currentOrganisation.sites || [] + res.render('reports/choose-site', { + sites + }) + }) - } else { + router.get('/reports/choose-pharmacies', (req, res) => { + const data = req.session.data + const currentOrganisation = res.locals.currentOrganisation - const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) - organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) - } + const pharmacies = data.organisations.filter((organisation) => organisation.companyId === currentOrganisation.id) - res.render('reports/choose-site', { - sites, - organisations + res.render('reports/choose-pharmacies', { + pharmacies }) + }) @@ -179,25 +185,18 @@ module.exports = (router) => { router.get('/reports/check', (req, res) => { const data = req.session.data const currentOrganisation = res.locals.currentOrganisation - const currentUser = res.locals.currentUser const siteIds = data.siteIdsToReport || [] const today = new Date() const days = 86400000 // number of milliseconds in a day - let sites, organisations + const pharmacyIdsToReport = data.pharmacyIdsToReport || [] - if (currentOrganisation) { + let sites, pharmacies - sites = currentOrganisation.sites + sites = (currentOrganisation.sites || []) .filter((site) => siteIds.includes(site.id)) - } else { - - const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) - - organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) - .filter((organisation) => siteIds.includes(organisation.id)) - } + pharmacies = data.organisations.filter((pharmacy) => pharmacyIdsToReport.includes(pharmacy.id)) const fromInput = data.from const toInput = data.to @@ -236,7 +235,7 @@ module.exports = (router) => { res.render('reports/check', { sites, - organisations, + pharmacies, from, to }) diff --git a/app/views/auth/medicare-sign-in.html b/app/views/auth/medicare-sign-in.html index 479a431b..7def5e0f 100644 --- a/app/views/auth/medicare-sign-in.html +++ b/app/views/auth/medicare-sign-in.html @@ -99,8 +99,8 @@

Testing area

Log in as:

diff --git a/app/views/auth/okta-sign-in.html b/app/views/auth/okta-sign-in.html index 0b913e3a..bb0a9173 100644 --- a/app/views/auth/okta-sign-in.html +++ b/app/views/auth/okta-sign-in.html @@ -38,8 +38,8 @@

Testing area

  • Recorder for an NHS trust
  • Recorder for multiple NHS trusts
  • Admin at 2 NHS trusts
  • -
  • Lead admin at a small chain of pharmacies
  • -
  • Lead admin for a large chain of pharmacies with the same name
  • +
  • Group admin at a small chain of pharmacies
  • +
  • Group admin for a large chain of pharmacies with the same name
  • Regional lead
  • Support user
  • Lead admin at a trust with all vaccines enabled
  • diff --git a/app/views/auth/select-mode.html b/app/views/auth/select-mode.html deleted file mode 100644 index 65e270ab..00000000 --- a/app/views/auth/select-mode.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends 'layout.html' %} - -{% set pageName = "Sign in" %} - -{% block content %} -
    -
    - -
    - - {{ radios({ - name: "loginMode", - fieldset: { - legend: { - text: "What do you want to do?", - size: "l", - isPageHeading: true - } - }, - hint: { - text: "As an administrator at multiple pharmacies or organisations you have 2 options. To switch between these, log out and log back in again." - }, - items: [ - { - text: "Use the service at 1 organisation", - value: "single", - hint: { - text: "For example, to record vaccinations and manage vaccines" - } - }, - { - text: "Create a report across multiple pharmacies or organisations", - value: "create-reports" - } - ] - }) }} - - {{ button({ - text: "Continue" - }) }} -
    - -
    -
    - -{% endblock %} diff --git a/app/views/auth/select-organisation.html b/app/views/auth/select-organisation.html index eaa3304a..caae94ea 100644 --- a/app/views/auth/select-organisation.html +++ b/app/views/auth/select-organisation.html @@ -2,16 +2,6 @@ {% set pageName = "Choose your organisation" %} -{% block beforeContent %} - {% if from === "select-mode" %} - {{ backLink({ - text: "Back", - href: "/auth/select-mode" - }) }} - {% endif %} -{% endblock %} - - {% block content %}
    diff --git a/app/views/home/_by-organisation.html b/app/views/home/_by-pharmacy.html similarity index 91% rename from app/views/home/_by-organisation.html rename to app/views/home/_by-pharmacy.html index 5096e321..3aa613d0 100644 --- a/app/views/home/_by-organisation.html +++ b/app/views/home/_by-pharmacy.html @@ -1,9 +1,9 @@ - + - {% for totalByOrganisation in (totalsByOrganisation | sort(false, false, "organisationName")) %} + {% for totalByOrganisation in (totalsByPharmacy | sort(false, false, "organisationName")) %} {% set organisation = data.organisations | findById(totalByOrganisation.organisationId) %}
    By organisationBy pharmacy
    - Organisation + Pharmacy Today @@ -20,7 +20,7 @@
    diff --git a/app/views/home/index.html b/app/views/home/index.html index fad8e48b..542427ec 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 %} @@ -42,7 +42,6 @@

    All vaccinations

    {% include "home/_vaccination-totals.html" %} - {% if totalVaccinationsRecorded > 0 %} {% set byDayHtml %} @@ -57,10 +56,11 @@

    All vaccinations

    {% include "home/_by-site.html" %} {% endset %} - {% set byOrganisationHtml %} - {% include "home/_by-organisation.html" %} + {% set byPharmacyHtml %} + {% include "home/_by-pharmacy.html" %} {% endset %} + {{ tabs({ items: [ { @@ -85,12 +85,12 @@

    All vaccinations

    } } if (totalsBySite | length) > 1, { - label: "By organisation", - id: "by-organisations", + label: "By pharmacy", + id: "by-pharmacies", panel: { - html: byOrganisationHtml + html: byPharmacyHtml } - } if (totalsByOrganisation | length) > 1 + } if (pharmacies | length) > 1 ] }) }} diff --git a/app/views/includes/header.html b/app/views/includes/header.html index 7b92876d..516a2f3c 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -11,25 +11,41 @@ active: (currentSection == "home") }), navigationItems) %} - {% if currentOrganisation %} + + {% if currentOrganisation.type === "Pharmacy HQ" %} + {% set navigationItems = (navigationItems.push({ + href: "/pharmacies", + text: "Pharmacies", + active: (currentSection == "pharmacies") + }), navigationItems) %} + + {% set navigationItems = (navigationItems.push({ + href: "/pharmacies/users", + text: "Users", + active: (currentSection == "pharmacies-users") + }), navigationItems) %} + {% endif %} + + + {% if currentOrganisation and (currentOrganisation.type != "Pharmacy HQ") %} {% set navigationItems = (navigationItems.push({ href: "/record-vaccinations", text: "Record vaccinations", active: (currentSection == "vaccinate") }), navigationItems) %} + {% endif %} - - {% if (["Lead administrator", "Administrator"] | arrayOrStringIncludes(organisationSetting.permissionLevel)) %} - {% set navigationItems = (navigationItems.push({ - href: "/vaccines", - text: "Vaccines", - active: (currentSection == "vaccines") - }), navigationItems) %} - {% endif %} + + {% if (["Lead administrator", "Administrator"] | arrayOrStringIncludes(organisationSetting.permissionLevel)) %} + {% set navigationItems = (navigationItems.push({ + href: "/vaccines", + text: "Vaccines", + active: (currentSection == "vaccines") + }), navigationItems) %} {% endif %} - - {% if currentOrganisation %} + + {% if currentOrganisation and (not currentOrganisation.type == "Pharmacy HQ") %} {% set navigationItems = (navigationItems.push({ href: "/records", text: "Records", @@ -37,8 +53,8 @@ }), navigationItems) %} {% endif %} - - {% if (["Lead administrator", "Administrator"] | arrayOrStringIncludes(organisationSetting.permissionLevel)) or data.currentMode == "reports" %} + + {% if (["Lead administrator", "Administrator"] | arrayOrStringIncludes(organisationSetting.permissionLevel)) or currentOrganisation.type === "Pharmacy HQ" %} {% set navigationItems = (navigationItems.push({ href: "/reports", text: "Reports", 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-user-check.html b/app/views/pharmacies/add-user-check.html new file mode 100644 index 00000000..b7e8c577 --- /dev/null +++ b/app/views/pharmacies/add-user-check.html @@ -0,0 +1,125 @@ +{% extends 'layout.html' %} + +{% set pageName = "Check" %} +{% set currentSection = "pharmacies" %} + + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + organisation.id + "/add-user-permission-level", + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
    +
    + +

    Check and {% if existingUserWithSameEmail %}reactivate{% else %}add{% endif %} user to {{ organisation.name }} ({{ organisation.id }})

    + + {% set nameText %} + {% if existingUser %} + {{ existingUser.firstName }} {{ existingUser.lastName }} + {% else %} + {{ data.firstName }} {{ data.lastName }} + {% endif %} + {% endset %} + + {{ summaryList({ + rows: [ + { + key: { + text: "Name" + }, + value: { + text: nameText + }, + actions: { + items: [ + { + href: "/pharmacies/" + organisation.id + "/add-user", + text: "Change", + visuallyHiddenText: "name" + } + ] + } + }, + { + key: { + text: "Email address" + }, + value: { + html: (existingUser.email if existingUser else data.email) + }, + actions: { + items: [ + { + href: "/pharmacies/" + organisation.id + "/add-user", + text: "Change", + visuallyHiddenText: "email address" + } + ] + } + }, + { + key: { + text: "Vaccinator" + }, + value: { + html: (data.vaccinator | capitalize) + }, + actions: { + items: [ + { + href: "/pharmacies/" + organisation.id + "/add-user-permission-level", + text: "Change", + visuallyHiddenText: "vaccinator status" + } + ] + } + }, + { + key: { + text: "Permission level" + }, + value: { + html: data.permissionLevel + }, + actions: { + items: [ + { + href: "/pharmacies/" + organisation.id + "/add-user-permission-level", + text: "Change", + visuallyHiddenText: "permission level" + } + ] + } + } + ] + }) }} + + + {% if existingUser %} +

    {{ existingUser.firstName }} will be sent this email:

    + {% else %} +

    {{ data.firstName }} will receive this welcome email with information about activating an account:

    + {% endif %} + +
    + {% if existingUserWithSameEmail %} + {% include "user-admin/_reactivation-email.html" %} + {% else %} + {% include "user-admin/_welcome-email.html" %} + {% endif %} +
    + +
    + {{ button({ + "text": "Confirm and send" + }) }} +
    + + +
    +
    +{% endblock %} diff --git a/app/views/pharmacies/add-user-permission-level.html b/app/views/pharmacies/add-user-permission-level.html new file mode 100644 index 00000000..bf0ee1cc --- /dev/null +++ b/app/views/pharmacies/add-user-permission-level.html @@ -0,0 +1,96 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Adding user to " + organisation.name %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + organisation.id + "/add-user" + }) }} +{% endblock %} + +{% block content %} +
    +
    +

    + Adding + {% if existingUser %} + {{ existingUser.firstName }} {{ existingUser.lastName }} + {% else %} + {{ data.firstName }} {{ data.lastName }} + {% endif %} + to {{ organisation.name }} ({{ organisation.id }}) +

    + +
    + + {{ radios({ + name: "permissionLevel", + idPrefix: "permission-level", + fieldset: { + legend: { + text: "Permission level", + size: "s" + } + }, + value: data.permissionLevel, + items: [ + { + value: "Recorder", + text: "Recorder", + hint: { + text: "Record vaccinations only" + } + }, + { + value: "Administrator", + text: "Administrator", + hint: { + text: "Record vaccinations, create reports and manage vaccines" + } + }, + { + value: "Lead administrator", + text: "Lead administrator", + hint: { + text: "Record vaccinations, create reports, manage vaccines and users" + } + } + ] + }) }} + + {{ radios({ + "name": "vaccinator", + "fieldset": { + "legend": { + "text": "Are they a vaccinator?", + size: "s" + } + }, + hint: { + text: "Vaccination records include the name of the person who gave the vaccination" + }, + value: data.vaccinator, + "items": [ + { + "value": "yes", + "text": "Yes", + id: "vaccinator" + }, + { + "value": "no", + "text": "No" + } + ] + }) }} + + + {{ button({ + text: "Continue" + }) }} + +
    + +
    +
    +{% endblock %} diff --git a/app/views/pharmacies/add-user.html b/app/views/pharmacies/add-user.html new file mode 100644 index 00000000..9d5a3833 --- /dev/null +++ b/app/views/pharmacies/add-user.html @@ -0,0 +1,132 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Add user to " + organisation.name + " (" + organisation.id + ")" %} + +{% 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, + hint: { + text: user.email + } + }), items) %} + {% endfor %} + + + + {% set newUserHtml %} + {{ input({ + label: { + text: "First name" + }, + name: "firstName", + classes: "nhsuk-input--width-20", + value: data.firstName + }) }} + + {{ input({ + label: { + text: "Last name" + }, + name: "lastName", + classes: "nhsuk-input--width-20", + value: data.lastName + }) }} + + {{ input({ + label: { + text: "Email address" + }, + name: "email", + type: "email", + value: data.email + }) }} + {% endset %} + + + + {% call fieldset({ + legend: { + text: "User", + size: "m", + classes: "nhsuk-u-margin-bottom-5" + } + }) %} + + {% if (users | length) > 10 %} + {{ input({ + id: "user-search", + name: "userSearch", + type: "search", + label: { + text: "Search" + }, + classes: "nhsuk-input--width-20", + attributes: { + "data-module": "app-radios-filter" + }, + formGroup: { + classes: "nhsuk-u-margin-bottom-4" + } + }) }} + {% endif %} + +
    + {{ radios({ + name: "userId", + value: data.userId, + items: items + }) }} +
    + + {{ radios({ + name: "userId", + value: data.userId, + items: [ + { + divider: "or" + }, + { + text: "Add a new user", + value: "add-new", + id: "add-new", + attributes: { + "data-no-filter": "" + }, + conditional: { + html: newUserHtml + } + } + ] + }) }} + + {% endcall %} + + {{ 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..f95b8cd4 --- /dev/null +++ b/app/views/pharmacies/check-selection.html @@ -0,0 +1,58 @@ +{% 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({ + text: "Confirm and add" + }) }} +
    + +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/deactivate.html b/app/views/pharmacies/deactivate.html new file mode 100644 index 00000000..87864833 --- /dev/null +++ b/app/views/pharmacies/deactivate.html @@ -0,0 +1,33 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% set pageName = "Deactivate " + pharmacy.name + " (" + pharmacy.id + ")" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + pharmacy.id + }) }} +{% endblock %} + +{% block content %} +
    +
    + +

    {{ pageName }}

    + +

    Once this pharmacy has been deactivated, users will no longer be able to record vaccinations for this pharmacy.

    + +

    For 90 days they will still be able to edit records and create reports. After 90 days the pharmacy will be closed and users will no longer have access to the Record a vaccination service for the pharmacy.

    + +
    + {{ button({ + "text": "Confirm and deactivate", + classes: "nhsuk-button--warning" + }) }} +
    + +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html new file mode 100644 index 00000000..e7e388d3 --- /dev/null +++ b/app/views/pharmacies/index.html @@ -0,0 +1,74 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% block content %} +
    +
    + + {% if added %} + {% set html %} +

    + {{ added | int | plural('pharmacy') }} added +

    +

    You can now add or assign users to these pharmacies.

    + {% endset %} + + {{ notificationBanner({ + html: html, + type: "success" + }) }} + {% endif %} + +

    Pharmacies

    + +

    Add new pharmacies, or select an existing pharmacy to add or manage users.

    + + {{ button({ + text: "Add pharmacies", + href: "/pharmacies/select" + }) }} + + + + + + + + + + + + {% for organisation in organisations %} + + + + + + {% endfor %} + + +
    Pharmacies added ({{ organisations | length }})
    + Name + + Vaccines + + Users +
    + {{ organisation.name }} ({{ organisation.id}}) + + {% set vaccinesEnabled = [] %} + {% for vaccine in organisation.vaccines %} + {% if vaccine.status == "enabled" %} + {% set vaccinesEnabled = (vaccinesEnabled.push(vaccine.name), vaccinesEnabled) %} + {% endif %} + {% endfor %} + + {{ vaccinesEnabled | sort | join(", ") }} + + {{ organisationUserCounts[organisation.id] }} +
    +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html new file mode 100644 index 00000000..98003614 --- /dev/null +++ b/app/views/pharmacies/pharmacy.html @@ -0,0 +1,136 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% set pageName = organisation.name %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies" + }) }} +{% endblock %} + +{% block content %} +
    +
    + + {% if added %} + {% set html %} +

    + User added +

    + {% endset %} + + {{ notificationBanner({ + html: html, + type: "success" + }) }} + {% endif %} + +

    {{ 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 + } + }, + { + key: { + text: "Vaccines" + }, + value: { + text: "COVID-19, flu" + } + } + ] + }) }} + + {% if organisation.status == 'Deactivated' %} +

    This pharmacy has been deactivated.

    + +

    It will be closed in 90 days.

    + {% else %} +

    Deactivate this pharmacy

    + + {% endif %} + +

    Users

    +
    +
    +
    +
    + + {% if organisation.status != 'Deactivated' %} + {{ button({ + href: "/pharmacies/" + organisation.id + "/add-user", + text: "Add user" + }) }} + {% endif %} + + {% if (users | length) > 0 %} + + + + + + + + + + + + + {% for user in users %} + + + + + + + + {% endfor %} + +
    + Name + + Permission level + + Vaccinator + + Status + + Actions +
    + + {{ user.firstName }} {{ user.lastName }} + + + {{ userOrganisationPermissions[user.id].permissionLevel }} + + {{ "Yes" if userOrganisationPermissions[user.id].vaccinator else "No" }} + + {{ userOrganisationPermissions[user.id].status }} + + Changepermission level for {{ user.firstName }} {{ user.lastName }} +
    + {% else %} +

    No users added yet.

    + {% endif %} + +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/select.html b/app/views/pharmacies/select.html new file mode 100644 index 00000000..ed3b05e4 --- /dev/null +++ b/app/views/pharmacies/select.html @@ -0,0 +1,109 @@ +{% 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-module": "app-checkbox-select-all", + "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-checkbox-filter" + }, + 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/add-to-check.html b/app/views/pharmacies/users/add-to-check.html new file mode 100644 index 00000000..254d626f --- /dev/null +++ b/app/views/pharmacies/users/add-to-check.html @@ -0,0 +1,104 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new", + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
    +
    + +

    Check and add user to pharmacy

    + + {% set pharmaciesHtml %} +
      + {% for pharmacy in pharmacies %} +
    • {{ pharmacy.name }}
    • + {% endfor %} +
    + {% endset %} + + {{ summaryList({ + rows: [ + { + key: { + text: "User" + }, + value: { + html: user.firstName + " " + user.lastName + "
    " + user.email + } + }, + { + key: { + text: "Pharmacy" + }, + value: { + html: pharmacy.name + " (" + pharmacy.id + ")" + }, + actions: { + items: [ + { + href: "/pharmacies/users/" + user.id + "/add-to", + text: "Change", + visuallyHiddenText: "pharmacy" + } + ] + } + } if data.groupAdministrator != "yes", + { + key: { + text: "Vaccinator" + }, + value: { + html: data.vaccinator | capitalize + }, + actions: { + items: [ + { + href: "/pharmacies/users/" + user.id + "/add-to-permission-level", + text: "Change", + visuallyHiddenText: "vaccinator" + } + ] + } + }, + { + key: { + text: "Permission level" + }, + value: { + html: data.permissionLevel + }, + actions: { + items: [ + { + href: "/pharmacies/users/" + user.id + "/add-to-permission-level", + text: "Change", + visuallyHiddenText: "permission level" + } + ] + } + } + ] + }) }} + +

    {{ user.firstName }} will receive this email telling them they now have access to the pharmacy:

    + +
    + {% include "user-admin/_welcome-email.html" %} +
    + +
    + {{ button({ + "text": "Confirm and send" + }) }} +
    + +
    +
    +{% endblock %} diff --git a/app/views/pharmacies/users/add-to-permission-level.html b/app/views/pharmacies/users/add-to-permission-level.html new file mode 100644 index 00000000..971049f7 --- /dev/null +++ b/app/views/pharmacies/users/add-to-permission-level.html @@ -0,0 +1,88 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = "Add pharmacies" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/" + user.id + "/add-to" + }) }} +{% endblock %} + +{% block content %} +
    +
    +
    + +

    What should {{ user.firstName }} {{ user.lastName}}'s role at {{ pharmacy.name }} be?

    + + {{ radios({ + name: "permissionLevel", + idPrefix: "permission-level", + fieldset: { + legend: { + text: "Permission level", + size: "s" + } + }, + value: data.permissionLevel, + items: [ + { + value: "Recorder", + text: "Recorder", + hint: { + text: "Record vaccinations only" + } + }, + { + value: "Administrator", + text: "Administrator", + hint: { + text: "Record vaccinations, create reports and manage vaccines" + } + }, + { + value: "Lead administrator", + text: "Lead administrator", + hint: { + text: "Record vaccinations, create reports, manage vaccines and users" + } + } + ] + }) }} + + {{ radios({ + name: "vaccinator", + fieldset: { + legend: { + text: "Are they a vaccinator?", + size: "s" + } + }, + hint: { + text: "Vaccination records include the name of the person who gave the vaccination" + }, + value: data.vaccinator, + items: [ + { + value: "yes", + text: "Yes", + id: "vaccinator" + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {{ button({ + text: "Continue" + }) }} + +
    +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/users/add-to.html b/app/views/pharmacies/users/add-to.html new file mode 100644 index 00000000..b14ecbbb --- /dev/null +++ b/app/views/pharmacies/users/add-to.html @@ -0,0 +1,48 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = "Add pharmacies" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/" + user.id + }) }} +{% endblock %} + +{% block content %} +
    +
    +
    + + {% set items = [] %} + + {% for pharmacy in pharmacies %} + {% set items = (items.push({ + text: pharmacy.name + ", " + pharmacy.address.postcode + " (" + pharmacy.id + ")", + value: pharmacy.id + }), items) %} + {% endfor %} + + {{ radios({ + id: "pharmacy-id", + name: "pharmacyId", + value: data.pharmacyId, + fieldset: { + legend: { + text: "Which pharmacy would you like to add " + user.firstName + " " + user.lastName + " to?", + size: "l" + } + }, + items: items + }) }} + + {{ button({ + text: "Continue" + }) }} + +
    +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/users/change-user-role.html b/app/views/pharmacies/users/change-user-role.html new file mode 100644 index 00000000..e6d8de71 --- /dev/null +++ b/app/views/pharmacies/users/change-user-role.html @@ -0,0 +1,92 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = "Add user to " + organisation.name + " (" + organisation.id + ")" %} + +{% block beforeContent %} + {% set backHref = "/pharmacies/users/" + user.id if data.from == "user" else "/pharmacies/" + pharmacy.id %} + {{ backLink({ + href: backHref + }) }} +{% endblock %} + +{% block content %} +
    +
    + +

    Change role for {{ user.firstName }} {{ user.lastName }} at {{ pharmacy.name }}

    + +
    + + {{ radios({ + name: "permissionLevel", + idPrefix: "permission-level", + fieldset: { + legend: { + text: "Permission level", + size: "s" + } + }, + value: role.permissionLevel, + items: [ + { + value: "Recorder", + text: "Recorder", + hint: { + text: "Record vaccinations only" + } + }, + { + value: "Administrator", + text: "Administrator", + hint: { + text: "Record vaccinations, create reports and manage vaccines" + } + }, + { + value: "Lead administrator", + text: "Lead administrator", + hint: { + text: "Record vaccinations, create reports, manage vaccines and users" + } + } + ] + }) }} + + {{ radios({ + "name": "vaccinator", + "fieldset": { + "legend": { + "text": "Are they a vaccinator?", + size: "s" + } + }, + hint: { + text: "Vaccination records include the name of the person who gave the vaccination" + }, + value: ("yes" if role.vaccinator else "no"), + "items": [ + { + "value": "yes", + "text": "Yes", + id: "vaccinator" + }, + { + "value": "no", + "text": "No" + } + ] + }) }} + + + {{ button({ + text: "Save" + }) }} + +
    + + +
    +
    +{% endblock %} diff --git a/app/views/pharmacies/users/check.html b/app/views/pharmacies/users/check.html new file mode 100644 index 00000000..d914dcbe --- /dev/null +++ b/app/views/pharmacies/users/check.html @@ -0,0 +1,130 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new", + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
    +
    + +

    Check and add {{ "group administrator" if data.permissionLevel == "Group administrator" else "user" }}

    + + {% set pharmaciesHtml %} +
      + {% for pharmacy in pharmacies %} +
    • {{ pharmacy.name }}
    • + {% endfor %} +
    + {% endset %} + + {{ summaryList({ + rows: [ + { + key: { + text: "Name" + }, + value: { + text: data.firstName + " " + data.lastName + }, + actions: { + items: [ + { + href: "/pharmacies/users/new", + text: "Change", + visuallyHiddenText: "name" + } + ] + } + }, + { + key: { + text: "Email address" + }, + value: { + html: data.email + }, + actions: { + items: [ + { + href: "/pharmacies/users/new", + text: "Change", + visuallyHiddenText: "email address" + } + ] + } + }, + { + key: { + text: "Vaccinator" + }, + value: { + html: data.vaccinator | capitalize + }, + actions: { + items: [ + { + href: "/pharmacies/users/new-permission-level", + text: "Change", + visuallyHiddenText: "vaccinator" + } + ] + } + } if data.groupAdministrator != "yes", + { + key: { + text: "Permission level" + }, + value: { + html: ("Group administrator" if data.groupAdministrator == "yes" else data.permissionLevel) + }, + actions: { + items: [ + { + href: "/pharmacies/users/new", + text: "Change", + visuallyHiddenText: "permission level" + } + ] + } + }, + { + key: { + text: "Pharmacies" + }, + value: { + html: pharmaciesHtml + }, + actions: { + items: [ + { + href: "/pharmacies/users/new-select-pharmacies", + text: "Change", + visuallyHiddenText: "pharmacies" + } + ] + } + } if data.groupAdministrator != "yes" + ] + }) }} + +

    {{ data.firstName }} will receive this welcome email telling them how to access the service:

    + +
    + {% include "user-admin/_welcome-email.html" %} +
    + +
    + {{ button({ + "text": "Confirm and send" + }) }} +
    + +
    +
    +{% endblock %} diff --git a/app/views/pharmacies/users/deactivate-from-pharmacy.html b/app/views/pharmacies/users/deactivate-from-pharmacy.html new file mode 100644 index 00000000..8f0c5461 --- /dev/null +++ b/app/views/pharmacies/users/deactivate-from-pharmacy.html @@ -0,0 +1,38 @@ +{% extends 'layout.html' %} + +{% set pageName = "Deactivate account" %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/" + user.id, + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
    +
    + + +

    Deactivate {{ user.firstName }} {{ user.lastName }} from {{ pharmacy.name }}

    + +

    Once you deactivate {{ user.firstName }} {{ user.lastName }} ({{ user.email}}), they cannot sign in and use NHS Record a vaccination at this pharmacy. They’ll receive an email to confirm their account has been deactivated.

    + +

    Their Okta account will remain active, so they can continue to access other services.

    + +

    You can reactivate their account anytime.

    + +
    + {{ button({ + "text": "Deactivate", + classes: "nhsuk-button--warning" + }) }} +
    + +
    +
    + + +{% endblock %} diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html new file mode 100644 index 00000000..4f9f4a29 --- /dev/null +++ b/app/views/pharmacies/users/index.html @@ -0,0 +1,85 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block content %} +
    +
    + +

    Users

    + +

    Add a new user or change the permissions of an existing user.

    + + {{ button({ + text: "Add user", + href: "/pharmacies/users/new" + }) }} + +

    Group administrators

    + + + + + + + + + + {% for user in groupAdministrators %} + + + + + + {% endfor %} + + +
    + Name + + Email +
    + {{ user.firstName }} {{ user.lastName }} + + {{ user.email }} +
    + + + + + + + + + + + + {% for user in users %} + {# See if they are a Group administrator #} + {% set groupRole = user.organisations | findById(currentOrganisation.id) %} + + + + + + + {% endfor %} + + +
    Users at individual pharmacies
    + Name + + Email + + Pharmacies +
    + {{ user.firstName }} {{ user.lastName }} + + {{ user.email }} + + {{ user.organisations | length }} +
    +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/users/new-permission-level.html b/app/views/pharmacies/users/new-permission-level.html new file mode 100644 index 00000000..e01d527d --- /dev/null +++ b/app/views/pharmacies/users/new-permission-level.html @@ -0,0 +1,93 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new-select-pharmacies-check" + }) }} +{% endblock %} + +{% block content %} +
    +
    + +
    + +

    {{ data.firstName }} {{ data.lastName }}’s role

    + +

    These roles will apply to the {{ data.pharmacyIds | length }} pharmacies.

    + + {{ radios({ + name: "vaccinator", + fieldset: { + legend: { + text: "Are they a vaccinator?", + size: "s" + } + }, + hint: { + text: "Vaccination records include the name of the person who gave the vaccination" + }, + value: data.vaccinator, + items: [ + { + value: "yes", + text: "Yes" + }, + { + value: "no", + text: "No" + } + ] + }) }} + + + {{ radios({ + name: "permissionLevel", + idPrefix: "permission-level", + value: data.permissionLevel, + fieldset: { + legend: { + text: "Permission level", + size: "s" + } + }, + items: [ + { + value: "Recorder", + text: "Recorder", + hint: { + text: "Record vaccinations only" + } + }, + { + "value": "Administrator", + "text": "Administrator", + "hint": { + "text": "Record vaccinations, create reports and manage vaccines" + } + }, + { + "value": "Lead administrator", + "text": "Lead administrator", + "hint": { + "text": "Record vaccinations, create reports, manage vaccines and users" + } + } + ], + "errorMessage": { + "text": permissionLevelError + } if permissionLevelError + }) }} + + + {{ button({ + "text": "Continue" + }) }} +
    + +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/users/new-select-pharmacies-check.html b/app/views/pharmacies/users/new-select-pharmacies-check.html new file mode 100644 index 00000000..cff3e136 --- /dev/null +++ b/app/views/pharmacies/users/new-select-pharmacies-check.html @@ -0,0 +1,55 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new-select-pharmacies" + }) }} +{% endblock %} + +{% block content %} +
    +
    + +

    Check pharmacies

    + +

    These are the pharmacies you will give {{ data.firstName }} {{ data.lastName }} access to.

    + +
    + {% set rows = [] %} + + {% for pharmacy in pharmacies %} + + {% set removeHtml %} + {{ button({ + text: "Remove", + classes: "nhsuk-button--secondary nhsuk-button--small nhsuk-u-margin-bottom-0" + }) }} + {% endset %} + + {% set rows = (rows.push([ + { text: pharmacy.name + ", " + pharmacy.address.postcode + " (" + pharmacy.id + ")" }, + { html: removeHtml } + ]), rows) %} + {% endfor %} + + + {{ table({ + head: [ + { text: "Pharmacy" }, + { text: "" } + ], + rows: rows + }) }} + + + {{ button({ + "text": "Continue" + }) }} +
    + +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/users/new-select-pharmacies.html b/app/views/pharmacies/users/new-select-pharmacies.html new file mode 100644 index 00000000..a1d0156f --- /dev/null +++ b/app/views/pharmacies/users/new-select-pharmacies.html @@ -0,0 +1,72 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new" + }) }} +{% endblock %} + +{% block content %} +
    +
    + +

    Select pharmacies

    + +

    You can invite the new user to 1 or more of your pharmacies.

    + +
    + + {% 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: "Which pharmacies do you want to add " + data.firstName + " " + data.lastName + " to?", + size: "m" + } + }) %} + + {{ input({ + id: "pharmacy-search", + name: "pharmacySearch", + type: "search", + label: { + text: "Search" + }, + classes: "nhsuk-input--width-20", + attributes: { + "data-module": "app-checkbox-filter" + }, + formGroup: { + classes: "nhsuk-u-margin-bottom-4" + } + }) }} + +
    + {{ checkboxes({ + id: "pharmacy-ids", + name: "pharmacyIds", + values: data.pharmacyIds, + items: items + }) }} +
    + + {% endcall %} + + {{ button({ + "text": "Continue" + }) }} +
    + +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html new file mode 100644 index 00000000..c835c3fc --- /dev/null +++ b/app/views/pharmacies/users/new.html @@ -0,0 +1,89 @@ +{% 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" + }, + "id": "email", + "name": "email", + type: "email", + value: data.email + }) }} + + {{ radios({ + idPrefix: "group-admin", + name: "groupAdministrator", + value: data.groupAdministrator, + fieldset: { + legend: { + text: "Permission level", + isPageHeading: true + } + }, + items: [ + { + value: "yes", + text: "Group administrator for Peak Pharmacy" + }, + { + divider: "or" + }, + { + text: "I want to add them to individual pharmacies", + value: "no" + } + ] + }) }} + + + {{ 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..973e3e2a --- /dev/null +++ b/app/views/pharmacies/users/user.html @@ -0,0 +1,130 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = user.firstName + " " + user.lastName %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users" + }) }} +{% endblock %} + +{% block content %} +
    +
    + + {% if addedToPharmacy %} + {% set html %} +

    + Added to pharmacy +

    +

    The user can now log in to {{ addedToPharmacy.name }}

    + {% endset %} + + {{ notificationBanner({ + html: html, + type: "success" + }) }} + {% endif %} + + {% if deactivatedFromPharmacy %} + {% set html %} +

    + Deactivated from pharmacy +

    +

    The user has been deactivated from {{ deactivatedFromPharmacy.name }}

    + {% endset %} + + {{ notificationBanner({ + html: html, + type: "success" + }) }} + {% endif %} + + +

    {{ pageName }}

    + + {{ summaryList({ + rows: [ + { + key: { + text: "Email" + }, + value: { + text: user.email + } + } + ] + }) }} + +
    +
    +
    +
    + + {% if (pharmacyRoles | length) > 0 %} + + + + + + + + + + + + + + {% for organisation in pharmacyRoles %} + {% set org = data.organisations | findById(organisation.id) %} + + + + + + + + + {% endfor %} + +
    Access and permission levels
    + Pharmacy + + Permission level + + Vaccinator + + Status +
    + + {{ org.name }} ({{ organisation.id }}) + + + {{ organisation.permissionLevel }} + + {{ "Yes" if organisation.vaccinator else "No" }} + + {{ organisation.status }} + + Changepermission level at {{ organisation.name }} + + Deactivate from {{ organisation.name }} +
    + + {% if totalPharmaciesAtOrganisation > (user.organisations | length) %} + {{ button({ + text: "Add to another pharmacy", + classes: "nhsuk-button--secondary", + href: "/pharmacies/users/" + user.id + "/add-to" + }) }} + {% endif %} + + + {% endif %} + +
    +
    + +{% endblock %} diff --git a/app/views/pharmacies/users/welcome-email-group-admin.html b/app/views/pharmacies/users/welcome-email-group-admin.html new file mode 100644 index 00000000..53643e02 --- /dev/null +++ b/app/views/pharmacies/users/welcome-email-group-admin.html @@ -0,0 +1,31 @@ +

    From: NHS Record a vaccination service (RAVS)
    + Subject: Start using Record a vaccination at (company name) +

    +
    + +

    Dear (first name) (last name),

    + +

    You’ve been invited to use the NHS Record a vaccination service as a group administrator for (company name).

    + +

    Here’s what you need to do next:

    + +

    1. Activate your Okta account

    + +

    We’ve created an Okta account for you to securely access the service.

    + +

    You’ll receive a 'Welcome to Okta' email (from noreply@okta.com).

    + +

    Activate the link within 7 days.

    + +

    If you cannot find the email, check your spam or junk.

    + +

    2. Log in to the service

    + +

    Once you've activated your Okta account, log in to www.ravs.england.nhs.uk using your Okta username and password.

    + +

    You can also access the service through your Okta account by selecting 'RAVS (PROD) app'.

    + +

    Kind regards,
    + NHS Record a vaccination

    + + diff --git a/app/views/reports/check.html b/app/views/reports/check.html index bd4e00ca..97546922 100644 --- a/app/views/reports/check.html +++ b/app/views/reports/check.html @@ -52,10 +52,10 @@

    {{ pageName }}

    {% endset %} - {% set organisationsHtml %} + {% set pharmaciesHtml %}
      - {% for organisation in organisations | sort(false, false, "name") %} -
    • {{ organisation.name }} ({{ organisation.id }})
    • + {% for pharmacy in pharmacies | sort(false, false, "name") %} +
    • {{ pharmacy.name }} ({{ pharmacy.id }})
    • {% endfor %}
    {% endset %} @@ -132,7 +132,24 @@

    {{ pageName }}

    } ] } - } if sites, + } if (sites | length) > 0, + { + key: { + text: ("Pharmacies" if (data.pharmacyIdsToReport | length) > 1 else "Pharmacy") + }, + value: { + html: pharmaciesHtml + }, + actions: { + items: [ + { + href: "/reports/choose-pharmacies", + text: "Change", + visuallyHiddenText: "pharmacies" + } + ] + } + } if currentOrganisation.type == "Pharmacy HQ", { key: { text: ("Organisations" if (data.siteIdsToReport | length) > 1 else "Organisation") diff --git a/app/views/reports/choose-data.html b/app/views/reports/choose-data.html index 477c66a1..5076d211 100644 --- a/app/views/reports/choose-data.html +++ b/app/views/reports/choose-data.html @@ -240,52 +240,61 @@
    diff --git a/app/views/reports/choose-pharmacies.html b/app/views/reports/choose-pharmacies.html new file mode 100644 index 00000000..bbd552c4 --- /dev/null +++ b/app/views/reports/choose-pharmacies.html @@ -0,0 +1,76 @@ +{% extends 'layout.html' %} + +{% set pageName = "Choose pharmacies" %} + +{% set currentSection = "reports" %} + +{% block beforeContent %} + {{ backLink({ + href: "/reports/choose-vaccines", + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
    +
    + +
    + + {% set items = [] %} + + {% set items = (items.push({ + text: "All " + (pharmacies|length) + " pharmacies", + value: "all", + attributes: { + "data-module": "app-checkbox-select-all", + "data-select-all": "true" + } + }), items) %} + + {% set items = (items.push({ + divider: "or" + }), items) %} + + + {% for pharmacy in pharmacies | sort(false, false, "name") %} + + {% if pharmacy.address %} + {% set hint = { + text: pharmacy.address.line1 + ", " + pharmacy.address.town + ", " + pharmacy.address.postcode + } %} + {% endif %} + + {% set items = (items.push({ + value: pharmacy.id, + text: pharmacy.name + " (" + pharmacy.id + ")", + hint: hint + }), items) %} + {% endfor %} + + {{ checkboxes({ + idPrefix: "pharmacy-to-report", + name: "pharmacyIdsToReport", + fieldset: { + legend: { + text: pageName, + classes: "nhsuk-fieldset__legend--l", + isPageHeading: true + } + }, + values: data.pharmacyIdsToReport, + items: items + }) }} + + + {{ button({ + text: "Continue" + }) }} + +
    + +
    +
    + +{% endblock %} + diff --git a/app/views/reports/choose-site.html b/app/views/reports/choose-site.html index aed4f930..f95ff530 100644 --- a/app/views/reports/choose-site.html +++ b/app/views/reports/choose-site.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} -{% set pageName = ("Choose sites" if sites else "Choose organisations") %} +{% set pageName = "Choose sites" %} {% set currentSection = "reports" %} @@ -19,12 +19,13 @@ {% set items = [] %} - {% if ((sites | length) > 2) or ((organisations | length) > 2) %} + {% if ((sites | length) > 2) %} {% set items = (items.push({ - text: "All " + ((sites|length) if sites else (organisations|length)) + " " + ("sites" if sites else "organisations"), + text: "All " + (sites|length) + " " + ("sites" if sites else "site"), value: "all", attributes: { + "data-module": "app-checkbox-select-all", "data-select-all": "true" } }), items) %} @@ -35,17 +36,17 @@ {% endif %} - {% for siteOrOrg in ((sites if sites else organisations) | sort(false, false, "name")) %} + {% for site in (sites | sort(false, false, "name")) %} - {% if siteOrOrg.address %} + {% if site.address %} {% set hint = { - text: siteOrOrg.address.line1 + ", " + siteOrOrg.address.town + ", " + siteOrOrg.address.postcode + text: site.address.line1 + ", " + site.address.town + ", " + site.address.postcode } %} {% endif %} {% set items = (items.push({ - value: siteOrOrg.id, - text: siteOrOrg.name + " (" + siteOrOrg.id + ")", + value: site.id, + text: site.name + " (" + site.id + ")", hint: hint }), items) %} {% endfor %} @@ -74,38 +75,5 @@ - - {% endblock %} diff --git a/app/views/reports/choose-vaccines.html b/app/views/reports/choose-vaccines.html index 8c60d5e4..c695571e 100644 --- a/app/views/reports/choose-vaccines.html +++ b/app/views/reports/choose-vaccines.html @@ -15,7 +15,7 @@ {% block content %}
    -
    + {% set items = [] %}