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..b4fa4e0f 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", @@ -8340,6 +8343,7 @@ module.exports = [ }, { id: 'PDL93', + companyId: "P0191N", name: 'Silverfields Chemists', sites: [ { @@ -8484,9 +8488,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 +8531,7 @@ module.exports = [ { id: 'FX4825', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '104 Bow Street', town: 'London', @@ -8541,6 +8559,7 @@ module.exports = [ { id: 'FX7314', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '99 Flowers Road', town: 'London', @@ -8568,6 +8587,7 @@ module.exports = [ { id: 'FX9151', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '12 Church Road', town: 'London', @@ -8595,6 +8615,7 @@ module.exports = [ { id: 'FQ2525', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '1 Granary Road', town: 'London', @@ -8622,6 +8643,7 @@ module.exports = [ { id: 'FW1941', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '8 Manchester Road', town: 'London', @@ -8649,6 +8671,7 @@ module.exports = [ { id: 'FP9824', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '12 John Robinson Road', town: 'London', @@ -8676,6 +8699,7 @@ module.exports = [ { id: 'FP1812', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '18 Church Road', town: 'London', @@ -8703,6 +8727,7 @@ module.exports = [ { id: "FA7K23", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "45 High Street", town: "Manchester", @@ -8730,6 +8755,7 @@ module.exports = [ { id: "FG2R56", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "78 Queen Street", town: "Birmingham", @@ -8757,6 +8783,7 @@ module.exports = [ { id: "FH9P12", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "23 Station Road", town: "Leeds", @@ -8784,6 +8811,7 @@ module.exports = [ { id: "FJ4M89", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "156 Market Street", town: "Liverpool", @@ -8811,6 +8839,7 @@ module.exports = [ { id: "FK5N34", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "89 Park Lane", town: "Bristol", @@ -8838,6 +8867,7 @@ module.exports = [ { id: "FL7Q67", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "34 Castle Street", town: "Edinburgh", @@ -8865,6 +8895,7 @@ module.exports = [ { id: "FM8R23", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "67 Main Street", town: "Glasgow", @@ -8892,6 +8923,7 @@ module.exports = [ { id: "FN9S45", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "112 Church Road", town: "Cardiff", @@ -8919,6 +8951,7 @@ module.exports = [ { id: "FP2T78", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "45 Bridge Street", town: "Newcastle", @@ -8946,6 +8979,7 @@ module.exports = [ { id: "FQ3U12", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "28 Victoria Road", town: "Sheffield", @@ -8973,6 +9007,7 @@ module.exports = [ { id: "FR4V56", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "91 Oxford Street", town: "Nottingham", @@ -9000,6 +9035,7 @@ module.exports = [ { id: "FS5W89", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "15 King Street", town: "Leicester", @@ -9027,6 +9063,7 @@ module.exports = [ { id: "FT6X34", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "203 London Road", town: "Southampton", @@ -9077,5 +9114,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..4b7ed9ce --- /dev/null +++ b/app/routes/pharmacies.js @@ -0,0 +1,380 @@ +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: [], + 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) + }) + + + + res.render('pharmacies/users/index', { + users + }) + }) + + 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 + const companyId = res.locals.currentOrganisation.id + + // 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 || [] + const companyId = res.locals.currentOrganisation.id + + // 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 || [] + const companyId = res.locals.currentOrganisation.id + + // 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 (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/users/:id',(req, res) => { + const data = req.session.data + const id = req.params.id + const user = data.users.find((user) => user.id === id) + + res.render('pharmacies/users/user', { + user + }) + }) + + + router.get('/pharmacies/:id/add-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/: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 + }) + }) + +} 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/apply/pharmacies.html b/app/views/apply/pharmacies.html index 1aacc662..8d65fa40 100644 --- a/app/views/apply/pharmacies.html +++ b/app/views/apply/pharmacies.html @@ -36,7 +36,10 @@

Select pharmacies

}), items) %} {% endfor %} -
+ {% call fieldset({ legend: { @@ -58,6 +61,7 @@

Select pharmacies

text: "Select all " + (pharmacies | length), value: "", attributes: { + "data-module": "app-checkbox-select-all", "data-select-all": "true" } }, @@ -78,7 +82,7 @@

Select pharmacies

}, classes: "nhsuk-input--width-20", attributes: { - "data-module": "app-filter-checkboxes" + "data-module": "app-checkbox-filter" }, formGroup: { classes: "nhsuk-u-margin-bottom-4" @@ -86,7 +90,7 @@

Select pharmacies

}) }} {% endif %} -
+
{{ checkboxes({ id: "pharmacy-ids", name: "pharmacyIds", @@ -98,7 +102,7 @@

Select pharmacies

{% endcall %} -

+

{{ button({ text: "Confirm" @@ -108,94 +112,4 @@

Select pharmacies

- - - {% endblock %} 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..a0f6a913 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: "Manage 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/index.html b/app/views/pharmacies/index.html new file mode 100644 index 00000000..591eedec --- /dev/null +++ b/app/views/pharmacies/index.html @@ -0,0 +1,71 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% block content %} +
+
+ + {% if added %} + {% set html %} +

+ {{ added }} pharmacies added +

+

You can now add or assign users to these pharmacies.

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

Pharmacies

+ +

Add new pharmacies or manage an existing pharmacy.

+ + {{ button({ + text: "Add pharmacies", + href: "/pharmacies/select" + }) }} + + + + + + + + + + + + {% for organisation in organisations %} + + + + + + {% endfor %} + + +
Pharmacies added ({{ organisations | length }})
+ Name + + Vaccines + + Users +
+ {{ organisation.name }} ({{ organisation.id}}) + + {% if loop.index0 == 0 %} + Not yet added + {% else %} + COVID-19, flu + {% endif %} + + {{ organisationUserCounts[organisation.id] }} +
+
+
+ +{% endblock %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html new file mode 100644 index 00000000..b817a217 --- /dev/null +++ b/app/views/pharmacies/pharmacy.html @@ -0,0 +1,125 @@ +{% 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" + } + } + ] + }) }} + +

Deactivate this pharmacy

+ +

Users

+
+
+
+
+ + {{ button({ + href: "/pharmacies/" + organisation.id + "/add-user", + text: "Add user" + }) }} + + {% 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 }} +
+ {% 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/check.html b/app/views/pharmacies/users/check.html new file mode 100644 index 00000000..93ab9861 --- /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 with information about activating an account:

+ +
+ {% include "user-admin/_welcome-email.html" %} +
+ +
+ {{ button({ + "text": "Confirm and send" + }) }} +
+ +
+
+{% endblock %} diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html new file mode 100644 index 00000000..f0f12a29 --- /dev/null +++ b/app/views/pharmacies/users/index.html @@ -0,0 +1,48 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block content %} +
+
+ +

Users

+ +

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

+ + {{ button({ + text: "Add user", + href: "/pharmacies/users/new" + }) }} + + + + + + + + + + + + {% for user in users %} + + + + + {% endfor %} + + +
Current users
+ Name + + Email +
+ {{ user.firstName }} {{ user.lastName }} + + {{ user.email }} +
+
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/new-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..1be54372 --- /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’ll add them to individual pharmacies later", + 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..00b831d2 --- /dev/null +++ b/app/views/pharmacies/users/user.html @@ -0,0 +1,92 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = user.firstName + " " + user.lastName %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ + {{ summaryList({ + rows: [ + { + key: { + text: "Email" + }, + value: { + text: user.email + } + } + ] + }) }} + +
+
+
+
+ + + + + + + + + + + + + + {% for organisation in user.organisations %} + {% set org = data.organisations | findById(organisation.id) %} + + + + + + + + {% endfor %} + +
Access and permission levels
+ Pharmacy + + Permission level + + Vaccinator + + Status +
+ + {{ org.name }} ({{ organisation.id }}) + + + {{ organisation.permissionLevel }} + + {{ organisation.vaccinator }} + + {{ organisation.status }} + + Deactivate +
+ + {{ button({ + text: "Add to another pharmacy", + classes: "nhsuk-button--secondary" + }) }} + +

Deactivate user from all pharmacies

+ +
+
+ +{% endblock %} 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 = [] %}