From bf9401537c895fb93a97d701997087951931d903 Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 7 Jul 2026 15:26:36 +0100 Subject: [PATCH 1/6] Add breast features review later button and prompt page --- app/assets/javascript/expandable-sections.js | 103 ++++++++++++------ app/routes/events.js | 74 ++++++++++++- .../review-after-imaging-breast-features.html | 46 ++++++++ .../events/review-medical-information.html | 5 + 4 files changed, 189 insertions(+), 39 deletions(-) create mode 100644 app/views/events/review-after-imaging-breast-features.html diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 9826e0bf..6d3b84e9 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -5,6 +5,54 @@ document.addEventListener('DOMContentLoaded', function () { // console.log('Expandable sections script loaded') // Debug log const sections = document.querySelectorAll('.js-expandable-section') const completedSections = new Set() + const reviewAfterImagingField = document.querySelector( + 'input[name="event[workflowStatus][review-breast-features-after-imaging]"]' + ) + + function setReviewAfterImagingFlag(shouldReviewAfterImaging) { + if (!reviewAfterImagingField) { + return + } + + reviewAfterImagingField.value = shouldReviewAfterImaging ? 'yes' : '' + } + + function completeSectionAndContinue(section, index) { + // Mark current section as completed + completedSections.add(index) + + // Determine current status and set new status + const currentStatus = getCurrentSectionStatus(section) + let newStatus + + if (currentStatus === 'Incomplete') { + newStatus = 'Complete' + } else if (currentStatus === 'To review') { + newStatus = 'Reviewed' + } else if (currentStatus === 'Reviewed') { + newStatus = 'Reviewed' // Keep as reviewed + } else { + newStatus = 'Complete' // Default fallback + } + + // Update the status for this section + updateSectionStatus(section, newStatus) + + // Save status to sessionStorage + const sectionId = section.getAttribute('id') + if (sectionId && window.saveSectionStatus) { + window.saveSectionStatus(sectionId, newStatus) + } + + // Close current section + section.removeAttribute('open') + + // Find and open the next incomplete section after this one + openNextIncompleteSection(index, sections, completedSections) + + // Update progress counter + updateProgress(sections, completedSections) + } sections.forEach(function (section, index) { const content = section.querySelector('.nhsuk-details__text') @@ -16,7 +64,10 @@ document.addEventListener('DOMContentLoaded', function () { // Create button container const buttonContainer = document.createElement('div') - buttonContainer.className = 'nhsuk-form-group' + buttonContainer.className = 'nhsuk-form-group nhsuk-button-group' + + const sectionId = section.getAttribute('id') + const isBreastFeaturesSection = sectionId === 'breast-features' // Create button const button = document.createElement('button') @@ -30,49 +81,35 @@ document.addEventListener('DOMContentLoaded', function () { buttonContainer.appendChild(button) + if (isBreastFeaturesSection) { + const reviewAfterImagingButton = document.createElement('button') + reviewAfterImagingButton.className = + 'nhsuk-button nhsuk-button--secondary nhsuk-u-margin-bottom-0 nhsuk-u-margin-top-3' + reviewAfterImagingButton.type = 'button' + reviewAfterImagingButton.textContent = 'Review after imaging' + + buttonContainer.appendChild(reviewAfterImagingButton) + + reviewAfterImagingButton.addEventListener('click', function () { + setReviewAfterImagingFlag(true) + completeSectionAndContinue(section, index) + }) + } + // Append HR first, then button container content.appendChild(hr) content.appendChild(buttonContainer) // Add click handler button.addEventListener('click', function () { - // Mark current section as completed - completedSections.add(index) - - // Determine current status and set new status - const currentStatus = getCurrentSectionStatus(section) - let newStatus - - if (currentStatus === 'Incomplete') { - newStatus = 'Complete' - } else if (currentStatus === 'To review') { - newStatus = 'Reviewed' - } else if (currentStatus === 'Reviewed') { - newStatus = 'Reviewed' // Keep as reviewed - } else { - newStatus = 'Complete' // Default fallback + if (isBreastFeaturesSection) { + setReviewAfterImagingFlag(false) } - // Update the status for this section - updateSectionStatus(section, newStatus) - - // Save status to sessionStorage - const sectionId = section.getAttribute('id') - if (sectionId && window.saveSectionStatus) { - window.saveSectionStatus(sectionId, newStatus) - } + completeSectionAndContinue(section, index) // Update button text based on new status updateButtonText(section, button) - - // Close current section - section.removeAttribute('open') - - // Find and open the next incomplete section after this one - openNextIncompleteSection(index, sections, completedSections) - - // Update progress counter - updateProgress(sections, completedSections) }) } }) diff --git a/app/routes/events.js b/app/routes/events.js index 419636f3..2847835d 100644 --- a/app/routes/events.js +++ b/app/routes/events.js @@ -102,6 +102,18 @@ function captureSessionEndTime(data, eventId, userId) { // } module.exports = (router) => { + const getPostImagingDestinationUrl = (data, clinicId, eventId) => { + const shouldReviewBreastFeaturesAfterImaging = + data?.event?.workflowStatus?.['review-breast-features-after-imaging'] === + 'yes' + + if (shouldReviewBreastFeaturesAfterImaging) { + return `/clinics/${clinicId}/events/${eventId}/review-after-imaging-breast-features` + } + + return `/clinics/${clinicId}/events/${eventId}/check-information` + } + // Set clinics to active in nav for all urls starting with /clinics router.use('/clinics/:clinicId/events/:eventId', (req, res, next) => { const eventId = req.params.eventId @@ -1493,6 +1505,15 @@ module.exports = (router) => { referrerChain, scrollTo ) + + if (!data.event.workflowStatus) { + data.event.workflowStatus = {} + } + + if (req.query.fromPostImagingBreastFeatures === '1') { + data.event.workflowStatus['review-breast-features-after-imaging'] = '' + } + res.redirect(modalBreakout(returnUrl)) } ) @@ -2157,6 +2178,49 @@ module.exports = (router) => { } ) + router.get( + '/clinics/:clinicId/events/:eventId/review-after-imaging-breast-features', + (req, res) => { + res.render('events/review-after-imaging-breast-features') + } + ) + + router.post( + '/clinics/:clinicId/events/:eventId/review-after-imaging-breast-features-answer', + (req, res) => { + const { clinicId, eventId } = req.params + const data = req.session.data + const choice = data?.event?.breastFeaturesAfterImagingDecision + + if (!choice) { + req.flash('error', { + text: 'Select whether to record breast features', + name: 'event[breastFeaturesAfterImagingDecision]' + }) + + return res.redirect( + `/clinics/${clinicId}/events/${eventId}/review-after-imaging-breast-features` + ) + } + + if (choice === 'yes') { + return res.redirect( + urlWithReferrer( + `/clinics/${clinicId}/events/${eventId}/medical-information/record-breast-features?fromPostImagingBreastFeatures=1`, + `/clinics/${clinicId}/events/${eventId}/check-information` + ) + ) + } + + if (!data.event.workflowStatus) { + data.event.workflowStatus = {} + } + + data.event.workflowStatus['review-breast-features-after-imaging'] = '' + res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) + } + ) + // Handle screening completion router.post( '/clinics/:clinicId/events/:eventId/imaging-answer', @@ -2205,7 +2269,7 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' - res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) + res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) } ) @@ -2784,9 +2848,7 @@ module.exports = (router) => { data.event.workflowStatus['take-images'] = 'completed' // Redirect to check information - return res.redirect( - `/clinics/${clinicId}/events/${eventId}/check-information` - ) + return res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) } // If custom details needed, go to details page @@ -2873,7 +2935,7 @@ module.exports = (router) => { data.event.workflowStatus['take-images'] = 'completed' // Redirect to check information - res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) + res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) } ) @@ -3035,7 +3097,7 @@ module.exports = (router) => { data.event.workflowStatus['take-images'] = 'completed' // Redirect to check information - res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) + res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) } ) diff --git a/app/views/events/review-after-imaging-breast-features.html b/app/views/events/review-after-imaging-breast-features.html new file mode 100644 index 00000000..3388fd38 --- /dev/null +++ b/app/views/events/review-after-imaging-breast-features.html @@ -0,0 +1,46 @@ +{# app/views/events/review-after-imaging-breast-features.html #} + +{% extends parentLayout or 'layout-appointment.html' %} + +{% set pageHeading = "Record breast features" %} +{% set showNavigation = true %} +{% set activeWorkflowStep = 'take-images' %} +{% set hideBackLink = true %} + +{% set formAction = './review-after-imaging-breast-features-answer' %} + +{% block pageContent %} + +

+ + {{ participant | getFullName }} + + {{ pageHeading }} +

+ + {{ radios({ + name: "event[breastFeaturesAfterImagingDecision]", + value: event.breastFeaturesAfterImagingDecision, + fieldset: { + legend: { + text: "Are there any breast features to add?", + classes: "nhsuk-fieldset__legend--m" + } + }, + items: [ + { + value: "yes", + text: "Yes, add features" + }, + { + value: "no", + text: "No, continue" + } + ] + } | populateErrors) }} + + {{ button({ + text: "Continue" + }) }} + +{% endblock %} diff --git a/app/views/events/review-medical-information.html b/app/views/events/review-medical-information.html index 0972de72..112643a5 100644 --- a/app/views/events/review-medical-information.html +++ b/app/views/events/review-medical-information.html @@ -27,6 +27,11 @@

value: "completed" }) }} + {{ appHiddenInput({ + name: "event[workflowStatus][review-breast-features-after-imaging]", + value: data.event.workflowStatus['review-breast-features-after-imaging'] + }) }} +
{{ button({ From 331b8565aaf45b9f595d548ebea6135b9c55d0a1 Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 7 Jul 2026 15:49:01 +0100 Subject: [PATCH 2/6] Small fix --- app/views/events/review-after-imaging-breast-features.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/review-after-imaging-breast-features.html b/app/views/events/review-after-imaging-breast-features.html index 3388fd38..817f894a 100644 --- a/app/views/events/review-after-imaging-breast-features.html +++ b/app/views/events/review-after-imaging-breast-features.html @@ -4,7 +4,7 @@ {% set pageHeading = "Record breast features" %} {% set showNavigation = true %} -{% set activeWorkflowStep = 'take-images' %} +{% set activeWorkflowStep = 'check-information' %} {% set hideBackLink = true %} {% set formAction = './review-after-imaging-breast-features-answer' %} From 00f21eab7842650a2911cad6e6babbbd020dbf26 Mon Sep 17 00:00:00 2001 From: rivalee Date: Wed, 8 Jul 2026 14:47:49 +0100 Subject: [PATCH 3/6] Content tweaks --- app/assets/javascript/expandable-sections.js | 98 ++++++++++++++++--- .../javascript/expanded-state-tracker.js | 17 +++- app/lib/utils/status.js | 8 +- app/routes/events.js | 13 ++- .../_includes/medical-information/index.njk | 10 +- .../review-after-imaging-breast-features.html | 15 +-- 6 files changed, 131 insertions(+), 30 deletions(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 6d3b84e9..5597b924 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -1,5 +1,9 @@ // app/assets/javascript/expandable-sections.js +const BREAST_FEATURES_SECTION_ID = 'breast-features' +const CONFIRM_AFTER_IMAGING_STATUS = 'Review after imaging' +const LEGACY_CONFIRM_AFTER_IMAGING_STATUS = 'Confirm after imaging' + // Handle expandable sections with completion tracking document.addEventListener('DOMContentLoaded', function () { // console.log('Expandable sections script loaded') // Debug log @@ -9,15 +13,25 @@ document.addEventListener('DOMContentLoaded', function () { 'input[name="event[workflowStatus][review-breast-features-after-imaging]"]' ) - function setReviewAfterImagingFlag(shouldReviewAfterImaging) { + function getReviewAfterImagingWorkflowStatus() { + if (!reviewAfterImagingField) { + return '' + } + + return reviewAfterImagingField.value || '' + } + + function setReviewAfterImagingFlag(statusValue) { if (!reviewAfterImagingField) { return } - reviewAfterImagingField.value = shouldReviewAfterImaging ? 'yes' : '' + reviewAfterImagingField.value = statusValue || '' } - function completeSectionAndContinue(section, index) { + function completeSectionAndContinue(section, index, options = {}) { + const forcedStatus = options.forcedStatus + // Mark current section as completed completedSections.add(index) @@ -25,7 +39,14 @@ document.addEventListener('DOMContentLoaded', function () { const currentStatus = getCurrentSectionStatus(section) let newStatus - if (currentStatus === 'Incomplete') { + if (forcedStatus) { + newStatus = forcedStatus + } else if ( + currentStatus === CONFIRM_AFTER_IMAGING_STATUS || + currentStatus === LEGACY_CONFIRM_AFTER_IMAGING_STATUS + ) { + newStatus = CONFIRM_AFTER_IMAGING_STATUS + } else if (currentStatus === 'Incomplete') { newStatus = 'Complete' } else if (currentStatus === 'To review') { newStatus = 'Reviewed' @@ -67,7 +88,24 @@ document.addEventListener('DOMContentLoaded', function () { buttonContainer.className = 'nhsuk-form-group nhsuk-button-group' const sectionId = section.getAttribute('id') - const isBreastFeaturesSection = sectionId === 'breast-features' + const isBreastFeaturesSection = sectionId === BREAST_FEATURES_SECTION_ID + + if (isBreastFeaturesSection) { + const reviewAfterImagingWorkflowStatus = + getReviewAfterImagingWorkflowStatus() + + if (reviewAfterImagingWorkflowStatus === 'yes') { + updateSectionStatus(section, CONFIRM_AFTER_IMAGING_STATUS) + if (window.saveSectionStatus) { + window.saveSectionStatus(sectionId, CONFIRM_AFTER_IMAGING_STATUS) + } + } else if (reviewAfterImagingWorkflowStatus === 'answered') { + updateSectionStatus(section, 'Reviewed') + if (window.saveSectionStatus) { + window.saveSectionStatus(sectionId, 'Reviewed') + } + } + } // Create button const button = document.createElement('button') @@ -91,8 +129,11 @@ document.addEventListener('DOMContentLoaded', function () { buttonContainer.appendChild(reviewAfterImagingButton) reviewAfterImagingButton.addEventListener('click', function () { - setReviewAfterImagingFlag(true) - completeSectionAndContinue(section, index) + setReviewAfterImagingFlag('yes') + completeSectionAndContinue(section, index, { + forcedStatus: CONFIRM_AFTER_IMAGING_STATUS + }) + updateButtonText(section, button) }) } @@ -103,7 +144,7 @@ document.addEventListener('DOMContentLoaded', function () { // Add click handler button.addEventListener('click', function () { if (isBreastFeaturesSection) { - setReviewAfterImagingFlag(false) + setReviewAfterImagingFlag('') } completeSectionAndContinue(section, index) @@ -125,14 +166,35 @@ document.addEventListener('DOMContentLoaded', function () { button.addEventListener('click', function (event) { // Mark all sections as completed sections.forEach(function (section, index) { + const sectionId = section.getAttribute('id') + const isBreastFeaturesSection = + sectionId === BREAST_FEATURES_SECTION_ID + // Add to completed set completedSections.add(index) // Determine current status and set new status const currentStatus = getCurrentSectionStatus(section) let newStatus - - if (currentStatus === 'Incomplete') { + const reviewAfterImagingWorkflowStatus = + getReviewAfterImagingWorkflowStatus() + + if ( + isBreastFeaturesSection && + reviewAfterImagingWorkflowStatus === 'yes' + ) { + newStatus = CONFIRM_AFTER_IMAGING_STATUS + } else if ( + isBreastFeaturesSection && + reviewAfterImagingWorkflowStatus === 'answered' + ) { + newStatus = 'Reviewed' + } else if ( + currentStatus === CONFIRM_AFTER_IMAGING_STATUS || + currentStatus === LEGACY_CONFIRM_AFTER_IMAGING_STATUS + ) { + newStatus = CONFIRM_AFTER_IMAGING_STATUS + } else if (currentStatus === 'Incomplete') { newStatus = 'Complete' } else if (currentStatus === 'To review') { newStatus = 'Reviewed' @@ -146,7 +208,6 @@ document.addEventListener('DOMContentLoaded', function () { updateSectionStatus(section, newStatus) // Save status to sessionStorage - const sectionId = section.getAttribute('id') if (sectionId && window.saveSectionStatus) { window.saveSectionStatus(sectionId, newStatus) } @@ -390,7 +451,12 @@ function updateSectionStatus(section, statusText) { if (statusElement) { // console.log('Found status element:', statusElement) - statusElement.textContent = statusText + const normalisedStatusText = + statusText === LEGACY_CONFIRM_AFTER_IMAGING_STATUS + ? CONFIRM_AFTER_IMAGING_STATUS + : statusText + + statusElement.textContent = normalisedStatusText // Update the tag colour class based on status // Reset all status classes first @@ -400,11 +466,13 @@ function updateSectionStatus(section, statusText) { 'nhsuk-tag--yellow' ) - if (statusText === 'Complete' || statusText === 'Reviewed') { + if (normalisedStatusText === 'Complete' || normalisedStatusText === 'Reviewed') { + statusElement.classList.add('nhsuk-tag--green') + } else if (normalisedStatusText === CONFIRM_AFTER_IMAGING_STATUS) { statusElement.classList.add('nhsuk-tag--green') - } else if (statusText === 'Incomplete') { + } else if (normalisedStatusText === 'Incomplete') { statusElement.classList.add('nhsuk-tag--blue') - } else if (statusText === 'To review') { + } else if (normalisedStatusText === 'To review') { statusElement.classList.add('nhsuk-tag--yellow') } diff --git a/app/assets/javascript/expanded-state-tracker.js b/app/assets/javascript/expanded-state-tracker.js index 5f6ac09d..c274f437 100644 --- a/app/assets/javascript/expanded-state-tracker.js +++ b/app/assets/javascript/expanded-state-tracker.js @@ -10,6 +10,8 @@ const STORAGE_KEY_PREFIX = 'expanded-sections-' const PENDING_EXPAND_KEY_PREFIX = 'pending-expand-' const STATUS_KEY_PREFIX = 'section-statuses-' + const CONFIRM_AFTER_IMAGING_STATUS = 'Review after imaging' + const LEGACY_CONFIRM_AFTER_IMAGING_STATUS = 'Confirm after imaging' function getStorageKey() { return STORAGE_KEY_PREFIX + window.location.pathname @@ -131,7 +133,12 @@ } if (statusElement) { - statusElement.textContent = statusText + const normalisedStatusText = + statusText === LEGACY_CONFIRM_AFTER_IMAGING_STATUS + ? CONFIRM_AFTER_IMAGING_STATUS + : statusText + + statusElement.textContent = normalisedStatusText // Update the tag colour class based on status statusElement.classList.remove( @@ -140,11 +147,13 @@ 'nhsuk-tag--yellow' ) - if (statusText === 'Complete' || statusText === 'Reviewed') { + if (normalisedStatusText === 'Complete' || normalisedStatusText === 'Reviewed') { + statusElement.classList.add('nhsuk-tag--green') + } else if (normalisedStatusText === CONFIRM_AFTER_IMAGING_STATUS) { statusElement.classList.add('nhsuk-tag--green') - } else if (statusText === 'Incomplete') { + } else if (normalisedStatusText === 'Incomplete') { statusElement.classList.add('nhsuk-tag--blue') - } else if (statusText === 'To review') { + } else if (normalisedStatusText === 'To review') { statusElement.classList.add('nhsuk-tag--yellow') } } diff --git a/app/lib/utils/status.js b/app/lib/utils/status.js index 6937927e..df1a708b 100644 --- a/app/lib/utils/status.js +++ b/app/lib/utils/status.js @@ -223,6 +223,8 @@ const getStatusTagColour = (status) => { 'incomplete': 'blue', 'complete': 'green', 'to_review': 'blue', + 'review_after_imaging': 'green', + 'confirm_after_imaging': 'green', 'reviewed': 'green', // Image reading @@ -327,7 +329,11 @@ const getStatusText = (status) => { prior_requested: 'Requested', prior_received: 'Received', prior_not_available: 'Not available', - prior_not_needed: 'Not needed' + prior_not_needed: 'Not needed', + + // Task list statuses + review_after_imaging: 'Review after imaging', + confirm_after_imaging: 'Review after imaging' // "technical-recall": 'Technical recall', // "recall-for-assesment": 'Recall for assessment', diff --git a/app/routes/events.js b/app/routes/events.js index 2847835d..c670ce0a 100644 --- a/app/routes/events.js +++ b/app/routes/events.js @@ -1511,7 +1511,8 @@ module.exports = (router) => { } if (req.query.fromPostImagingBreastFeatures === '1') { - data.event.workflowStatus['review-breast-features-after-imaging'] = '' + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' } res.redirect(modalBreakout(returnUrl)) @@ -2204,6 +2205,13 @@ module.exports = (router) => { } if (choice === 'yes') { + if (!data.event.workflowStatus) { + data.event.workflowStatus = {} + } + + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' + return res.redirect( urlWithReferrer( `/clinics/${clinicId}/events/${eventId}/medical-information/record-breast-features?fromPostImagingBreastFeatures=1`, @@ -2216,7 +2224,8 @@ module.exports = (router) => { data.event.workflowStatus = {} } - data.event.workflowStatus['review-breast-features-after-imaging'] = '' + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) } ) diff --git a/app/views/_includes/medical-information/index.njk b/app/views/_includes/medical-information/index.njk index 95281412..738996b5 100644 --- a/app/views/_includes/medical-information/index.njk +++ b/app/views/_includes/medical-information/index.njk @@ -181,6 +181,14 @@ {% set breastFeatures = event.medicalInformation.breastFeatures or rawBreastFeatures %} {% set breastFeaturesCount = breastFeatures | length %} {% set hasBreastFeatures = breastFeaturesCount > 0 %} +{% set reviewBreastFeaturesAfterImagingStatus = data.event.workflowStatus['review-breast-features-after-imaging'] %} + +{% set breastFeaturesStatus = "To review" %} +{% if reviewBreastFeaturesAfterImagingStatus == 'yes' %} + {% set breastFeaturesStatus = "Review after imaging" %} +{% elseif reviewBreastFeaturesAfterImagingStatus == 'answered' %} + {% set breastFeaturesStatus = "Reviewed" %} +{% endif %} {% set breastFeaturesContentsSummaryText %} {{ breastFeaturesCount or "No" }} breast {{ "feature" | pluralise(breastFeaturesCount) }} added @@ -214,7 +222,7 @@ subtitle: subHeading, html: breastFeaturesHtml, contentsSummary: breastFeaturesContentsSummaryText, - status: "To review" + status: breastFeaturesStatus }) }} {% endswitch %} diff --git a/app/views/events/review-after-imaging-breast-features.html b/app/views/events/review-after-imaging-breast-features.html index 817f894a..755ae10d 100644 --- a/app/views/events/review-after-imaging-breast-features.html +++ b/app/views/events/review-after-imaging-breast-features.html @@ -2,19 +2,17 @@ {% extends parentLayout or 'layout-appointment.html' %} -{% set pageHeading = "Record breast features" %} +{% set pageHeading = "Review breast features" %} {% set showNavigation = true %} {% set activeWorkflowStep = 'check-information' %} {% set hideBackLink = true %} +{% set gridColumn = "nhsuk-grid-column-two-thirds" %} {% set formAction = './review-after-imaging-breast-features-answer' %} {% block pageContent %}

- - {{ participant | getFullName }} - {{ pageHeading }}

@@ -23,18 +21,21 @@

value: event.breastFeaturesAfterImagingDecision, fieldset: { legend: { - text: "Are there any breast features to add?", + text: "Are there any breast features to add for " + (participant | getFullName) + "?", classes: "nhsuk-fieldset__legend--m" } }, + hint: { + text: "This includes non-surgical scars, moles and other features that may appear on mammogram images" + }, items: [ { value: "yes", - text: "Yes, add features" + text: "Yes" }, { value: "no", - text: "No, continue" + text: "No" } ] } | populateErrors) }} From cb7e3a6568fd6e9e2cbb9e8c5a6daae60f800acd Mon Sep 17 00:00:00 2001 From: rivalee Date: Mon, 13 Jul 2026 16:28:14 +0100 Subject: [PATCH 4/6] Add breast features diagram to the take images page --- app/assets/javascript/expandable-sections.js | 48 +++---- .../javascript/expanded-state-tracker.js | 2 +- app/lib/utils/status.js | 4 +- app/routes/events.js | 125 ++++++++++++++---- .../_includes/medical-information/index.njk | 10 +- app/views/events/check-information.html | 50 +++++-- app/views/events/images-automatic.html | 51 +++++++ .../events/images-before-mammography.html | 54 +++++++- app/views/events/images-manual.html | 54 +++++++- 9 files changed, 316 insertions(+), 82 deletions(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 5597b924..d1d5bff9 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -1,8 +1,7 @@ // app/assets/javascript/expandable-sections.js const BREAST_FEATURES_SECTION_ID = 'breast-features' -const CONFIRM_AFTER_IMAGING_STATUS = 'Review after imaging' -const LEGACY_CONFIRM_AFTER_IMAGING_STATUS = 'Confirm after imaging' +const REVIEW_AT_IMAGING_STATUS = 'Review at imaging' // Handle expandable sections with completion tracking document.addEventListener('DOMContentLoaded', function () { @@ -41,11 +40,6 @@ document.addEventListener('DOMContentLoaded', function () { if (forcedStatus) { newStatus = forcedStatus - } else if ( - currentStatus === CONFIRM_AFTER_IMAGING_STATUS || - currentStatus === LEGACY_CONFIRM_AFTER_IMAGING_STATUS - ) { - newStatus = CONFIRM_AFTER_IMAGING_STATUS } else if (currentStatus === 'Incomplete') { newStatus = 'Complete' } else if (currentStatus === 'To review') { @@ -95,9 +89,9 @@ document.addEventListener('DOMContentLoaded', function () { getReviewAfterImagingWorkflowStatus() if (reviewAfterImagingWorkflowStatus === 'yes') { - updateSectionStatus(section, CONFIRM_AFTER_IMAGING_STATUS) + updateSectionStatus(section, REVIEW_AT_IMAGING_STATUS) if (window.saveSectionStatus) { - window.saveSectionStatus(sectionId, CONFIRM_AFTER_IMAGING_STATUS) + window.saveSectionStatus(sectionId, REVIEW_AT_IMAGING_STATUS) } } else if (reviewAfterImagingWorkflowStatus === 'answered') { updateSectionStatus(section, 'Reviewed') @@ -124,14 +118,14 @@ document.addEventListener('DOMContentLoaded', function () { reviewAfterImagingButton.className = 'nhsuk-button nhsuk-button--secondary nhsuk-u-margin-bottom-0 nhsuk-u-margin-top-3' reviewAfterImagingButton.type = 'button' - reviewAfterImagingButton.textContent = 'Review after imaging' + reviewAfterImagingButton.textContent = 'Review at imaging' buttonContainer.appendChild(reviewAfterImagingButton) reviewAfterImagingButton.addEventListener('click', function () { setReviewAfterImagingFlag('yes') completeSectionAndContinue(section, index, { - forcedStatus: CONFIRM_AFTER_IMAGING_STATUS + forcedStatus: REVIEW_AT_IMAGING_STATUS }) updateButtonText(section, button) }) @@ -183,17 +177,12 @@ document.addEventListener('DOMContentLoaded', function () { isBreastFeaturesSection && reviewAfterImagingWorkflowStatus === 'yes' ) { - newStatus = CONFIRM_AFTER_IMAGING_STATUS + newStatus = REVIEW_AT_IMAGING_STATUS } else if ( isBreastFeaturesSection && reviewAfterImagingWorkflowStatus === 'answered' ) { newStatus = 'Reviewed' - } else if ( - currentStatus === CONFIRM_AFTER_IMAGING_STATUS || - currentStatus === LEGACY_CONFIRM_AFTER_IMAGING_STATUS - ) { - newStatus = CONFIRM_AFTER_IMAGING_STATUS } else if (currentStatus === 'Incomplete') { newStatus = 'Complete' } else if (currentStatus === 'To review') { @@ -292,7 +281,11 @@ function getCurrentSectionStatus(section) { function updateButtonText(section, button) { const currentStatus = getCurrentSectionStatus(section) - if (currentStatus === 'Reviewed' || currentStatus === 'Complete') { + if ( + currentStatus === 'Reviewed' || + currentStatus === 'Complete' || + currentStatus === REVIEW_AT_IMAGING_STATUS + ) { button.textContent = 'Next section' } else { button.textContent = 'Mark as reviewed' @@ -451,12 +444,7 @@ function updateSectionStatus(section, statusText) { if (statusElement) { // console.log('Found status element:', statusElement) - const normalisedStatusText = - statusText === LEGACY_CONFIRM_AFTER_IMAGING_STATUS - ? CONFIRM_AFTER_IMAGING_STATUS - : statusText - - statusElement.textContent = normalisedStatusText + statusElement.textContent = statusText // Update the tag colour class based on status // Reset all status classes first @@ -466,13 +454,15 @@ function updateSectionStatus(section, statusText) { 'nhsuk-tag--yellow' ) - if (normalisedStatusText === 'Complete' || normalisedStatusText === 'Reviewed') { - statusElement.classList.add('nhsuk-tag--green') - } else if (normalisedStatusText === CONFIRM_AFTER_IMAGING_STATUS) { + if ( + statusText === 'Complete' || + statusText === 'Reviewed' || + statusText === REVIEW_AT_IMAGING_STATUS + ) { statusElement.classList.add('nhsuk-tag--green') - } else if (normalisedStatusText === 'Incomplete') { + } else if (statusText === 'Incomplete') { statusElement.classList.add('nhsuk-tag--blue') - } else if (normalisedStatusText === 'To review') { + } else if (statusText === 'To review') { statusElement.classList.add('nhsuk-tag--yellow') } diff --git a/app/assets/javascript/expanded-state-tracker.js b/app/assets/javascript/expanded-state-tracker.js index c274f437..85c507ce 100644 --- a/app/assets/javascript/expanded-state-tracker.js +++ b/app/assets/javascript/expanded-state-tracker.js @@ -10,7 +10,7 @@ const STORAGE_KEY_PREFIX = 'expanded-sections-' const PENDING_EXPAND_KEY_PREFIX = 'pending-expand-' const STATUS_KEY_PREFIX = 'section-statuses-' - const CONFIRM_AFTER_IMAGING_STATUS = 'Review after imaging' + const CONFIRM_AFTER_IMAGING_STATUS = 'Review at imaging' const LEGACY_CONFIRM_AFTER_IMAGING_STATUS = 'Confirm after imaging' function getStorageKey() { diff --git a/app/lib/utils/status.js b/app/lib/utils/status.js index df1a708b..309fdbc3 100644 --- a/app/lib/utils/status.js +++ b/app/lib/utils/status.js @@ -332,8 +332,8 @@ const getStatusText = (status) => { prior_not_needed: 'Not needed', // Task list statuses - review_after_imaging: 'Review after imaging', - confirm_after_imaging: 'Review after imaging' + review_after_imaging: 'Review at imaging', + confirm_after_imaging: 'Review at imaging' // "technical-recall": 'Technical recall', // "recall-for-assesment": 'Recall for assessment', diff --git a/app/routes/events.js b/app/routes/events.js index c670ce0a..9fc97c9b 100644 --- a/app/routes/events.js +++ b/app/routes/events.js @@ -103,14 +103,6 @@ function captureSessionEndTime(data, eventId, userId) { module.exports = (router) => { const getPostImagingDestinationUrl = (data, clinicId, eventId) => { - const shouldReviewBreastFeaturesAfterImaging = - data?.event?.workflowStatus?.['review-breast-features-after-imaging'] === - 'yes' - - if (shouldReviewBreastFeaturesAfterImaging) { - return `/clinics/${clinicId}/events/${eventId}/review-after-imaging-breast-features` - } - return `/clinics/${clinicId}/events/${eventId}/check-information` } @@ -1460,6 +1452,50 @@ module.exports = (router) => { ) // Save breast features (includes converting JSON string to structured data) + const persistBreastFeaturesFromRaw = (data) => { + let errorCount = 0 + let hasBreastFeatures = false + + if (!data?.event) { + return { + errorCount, + hasBreastFeatures + } + } + + if (!data.event.medicalInformation) { + data.event.medicalInformation = {} + } + + const medicalInformation = data.event.medicalInformation + + if (medicalInformation.breastFeaturesRaw) { + try { + const rawFeatures = medicalInformation.breastFeaturesRaw + if (typeof rawFeatures === 'string') { + medicalInformation.breastFeatures = JSON.parse(rawFeatures) + delete medicalInformation.breastFeaturesRaw + console.log( + 'Converted breastFeaturesRaw to structured data and deleted raw data' + ) + } + } catch (error) { + console.warn('Failed to convert breastFeaturesRaw:', error) + errorCount++ + } + } + + const breastFeatures = medicalInformation.breastFeatures + if (Array.isArray(breastFeatures) && breastFeatures.length > 0) { + hasBreastFeatures = true + } + + return { + errorCount, + hasBreastFeatures + } + } + router.post( '/clinics/:clinicId/events/:eventId/medical-information/record-breast-features/save', (req, res) => { @@ -1471,24 +1507,10 @@ module.exports = (router) => { let conversionsCount = 0 let errorCount = 0 - // Convert breast features raw data - if (data.event?.medicalInformation?.breastFeaturesRaw) { - try { - const rawFeatures = data.event.medicalInformation.breastFeaturesRaw - if (typeof rawFeatures === 'string') { - data.event.medicalInformation.breastFeatures = - JSON.parse(rawFeatures) - // Delete the raw data once converted - delete data.event.medicalInformation.breastFeaturesRaw - conversionsCount++ - console.log( - 'Converted breastFeaturesRaw to structured data and deleted raw data' - ) - } - } catch (error) { - console.warn('Failed to convert breastFeaturesRaw:', error) - errorCount++ - } + const conversionResult = persistBreastFeaturesFromRaw(data) + errorCount = conversionResult.errorCount + if (conversionResult.hasBreastFeatures) { + conversionsCount++ } // Flash error message if needed @@ -2267,6 +2289,16 @@ module.exports = (router) => { } if (hasMultipleImages) { + const reviewAfterImagingSelected = + data.event?.workflowStatus?.['review-breast-features-after-imaging'] === + 'yes' + const conversionResult = persistBreastFeaturesFromRaw(data) + + if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' + } + return res.redirect( `/clinics/${clinicId}/events/${eventId}/images-repeats` ) @@ -2278,6 +2310,16 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' + const reviewAfterImagingSelected = + data.event?.workflowStatus?.['review-breast-features-after-imaging'] === + 'yes' + const conversionResult = persistBreastFeaturesFromRaw(data) + + if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' + } + res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) } ) @@ -2856,6 +2898,17 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' + const reviewAfterImagingSelected = + data.event?.workflowStatus?.[ + 'review-breast-features-after-imaging' + ] === 'yes' + const conversionResult = persistBreastFeaturesFromRaw(data) + + if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' + } + // Redirect to check information return res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) } @@ -2943,6 +2996,16 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' + const reviewAfterImagingSelected = + data.event?.workflowStatus?.['review-breast-features-after-imaging'] === + 'yes' + const conversionResult = persistBreastFeaturesFromRaw(data) + + if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' + } + // Redirect to check information res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) } @@ -3105,6 +3168,16 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' + const reviewAfterImagingSelected = + data.event?.workflowStatus?.['review-breast-features-after-imaging'] === + 'yes' + const conversionResult = persistBreastFeaturesFromRaw(data) + + if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' + } + // Redirect to check information res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) } diff --git a/app/views/_includes/medical-information/index.njk b/app/views/_includes/medical-information/index.njk index 738996b5..95281412 100644 --- a/app/views/_includes/medical-information/index.njk +++ b/app/views/_includes/medical-information/index.njk @@ -181,14 +181,6 @@ {% set breastFeatures = event.medicalInformation.breastFeatures or rawBreastFeatures %} {% set breastFeaturesCount = breastFeatures | length %} {% set hasBreastFeatures = breastFeaturesCount > 0 %} -{% set reviewBreastFeaturesAfterImagingStatus = data.event.workflowStatus['review-breast-features-after-imaging'] %} - -{% set breastFeaturesStatus = "To review" %} -{% if reviewBreastFeaturesAfterImagingStatus == 'yes' %} - {% set breastFeaturesStatus = "Review after imaging" %} -{% elseif reviewBreastFeaturesAfterImagingStatus == 'answered' %} - {% set breastFeaturesStatus = "Reviewed" %} -{% endif %} {% set breastFeaturesContentsSummaryText %} {{ breastFeaturesCount or "No" }} breast {{ "feature" | pluralise(breastFeaturesCount) }} added @@ -222,7 +214,7 @@ subtitle: subHeading, html: breastFeaturesHtml, contentsSummary: breastFeaturesContentsSummaryText, - status: breastFeaturesStatus + status: "To review" }) }} {% endswitch %} diff --git a/app/views/events/check-information.html b/app/views/events/check-information.html index 0d7ab8ce..8f4ad9dd 100644 --- a/app/views/events/check-information.html +++ b/app/views/events/check-information.html @@ -1,5 +1,7 @@ {% extends 'layout-appointment.html' %} +{% from '_components/notification-banner/macro.njk' import appNotificationBanner %} + {% set pageHeading = "Check information" %} {% set showNavigation = true %} @@ -19,6 +21,7 @@ #} {% set activeTab = 'review' %} +{% set showReviewAfterImagingReminder = data.event.workflowStatus['review-breast-features-after-imaging'] == 'yes' %} @@ -67,12 +70,14 @@ {% include "_includes/summary-lists/medical-info-summary.njk" %} {% endset %} - {{ card({ - heading: "Medical information", - headingLevel: "2", - feature: true, - descriptionHtml: medicalInfoSummaryHtml - }) }} +
+ {{ card({ + heading: "Medical information", + headingLevel: "2", + feature: true, + descriptionHtml: medicalInfoSummaryHtml + }) }} +
{% endset %} @@ -128,12 +133,14 @@ {% include "_includes/medical-information/breast-features.njk" %} {% endset %} - {{ card({ - heading: "Breast features", - headingLevel: "2", - feature: true, - descriptionHtml: breastFeaturesHtml - }) }} +
+ {{ card({ + heading: "Breast features", + headingLevel: "2", + feature: true, + descriptionHtml: breastFeaturesHtml + }) }} +
{# Other relevant information card #} {% set otherRelevantInformationHtml %} @@ -155,6 +162,25 @@

{{ pageHeading }}

+ {% if showReviewAfterImagingReminder %} + {% set breastFeaturesReminderHtml %} +

+ Are there any breast features to add for {{ participant | getFullName }}? +

+

+ This includes non-surgical scars, moles and other features that may appear on mammogram images +

+

+ Review breast features +

+ {% endset %} + + {{ appNotificationBanner({ + titleText: "Reminder to add breast features", + html: breastFeaturesReminderHtml + }) }} + {% endif %} +

To conclude this appointment, confirm this information is correct.

diff --git a/app/views/events/images-automatic.html b/app/views/events/images-automatic.html index e4f5b596..6594f6ee 100644 --- a/app/views/events/images-automatic.html +++ b/app/views/events/images-automatic.html @@ -48,8 +48,43 @@ {% block pageContent %} + {% set rawBreastFeatures = event.medicalInformation.breastFeaturesRaw | parseJsonString %} + {% set breastFeatures = event.medicalInformation.breastFeatures or rawBreastFeatures %} + {% set reviewAfterImagingStatus = data.event.workflowStatus['review-breast-features-after-imaging'] %} + {% if not reviewAfterImagingStatus %} + {% set reviewAfterImagingStatus = event.workflowStatus['review-breast-features-after-imaging'] %} + {% endif %} +

{{ pageHeading }}

+ {% set allowEdits = true %} +

Are there any breast features to add?

+
+ {# +

+ Click the diagram to add notable features such as scars, moles, or warts that image readers may need to be aware of. These could be observed by you or reported by the participant. +

+
+

Where is the feature?

+
+ #} +
+
+ {% include "_includes/breast-diagram-wrapper.njk" %} +
+ {% include "_includes/feature-popover.njk" %} + {% include "_includes/features-list.njk" %} +
+ + {{ button({ + text: "Save", + type: "button", + variant: "secondary", + attributes: { + id: "saveBtn" + } + }) }} + {% if data.event.workflowStatus["take-images"] == 'completed' %} @@ -123,6 +158,22 @@

Additional details

{% endblock %} {% block pageScripts %} + + + + + + {% set insetHtml %}

Mammography images will appear below once they have been received @@ -25,7 +77,6 @@

{{ pageHeading }}

html: insetHtml }) }} - {{ appHiddenInput({ name: "event[workflowStatus][awaiting-images]", value: "completed" @@ -42,7 +93,6 @@

{{ pageHeading }}

{% include "_includes/images/image-troubleshooting.njk" %} - {% endblock %} diff --git a/app/views/events/images-manual.html b/app/views/events/images-manual.html index a027441d..e901dacd 100644 --- a/app/views/events/images-manual.html +++ b/app/views/events/images-manual.html @@ -14,7 +14,12 @@ {% block pageContent %} -

{{ pageHeading }}

+ {% set rawBreastFeatures = event.medicalInformation.breastFeaturesRaw | parseJsonString %} + {% set breastFeatures = event.medicalInformation.breastFeatures or rawBreastFeatures %} + {% set reviewAfterImagingStatus = data.event.workflowStatus['review-breast-features-after-imaging'] %} + {% if not reviewAfterImagingStatus %} + {% set reviewAfterImagingStatus = event.workflowStatus['review-breast-features-after-imaging'] %} + {% endif %} {% set mammogramSource = event.mammogramDataTemp or event.mammogramData %} @@ -34,6 +39,8 @@

{{ pageHeading }}

{% endfor %} {% endif %} +

{{ pageHeading }}

+ {% if currentRoomName %}

Mammogram location: {{ currentRoomName }}. @@ -52,6 +59,51 @@

{{ pageHeading }}

{% endif %} + {% set allowEdits = true %} +

Are there any breast features to add?

+
+ {# +

+ Click the diagram to add notable features such as scars, moles, or warts that image readers may need to be aware of. These could be observed by you or reported by the participant. +

+
+

Where is the feature?

+
+ #} +
+
+ {% include "_includes/breast-diagram-wrapper.njk" %} +
+ {% include "_includes/feature-popover.njk" %} + {% include "_includes/features-list.njk" %} +
+ + {{ button({ + text: "Save", + type: "button", + variant: "secondary", + attributes: { + id: "saveBtn" + } + }) }} + + + + + {# {{ radios({ name: "event[mammogramDataTemp][machineRoom]", value: mammogramSource.machineRoom, From 51a42218f7f0dcebc10f5f61fc0a548b8bafd1ca Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 14 Jul 2026 16:11:58 +0100 Subject: [PATCH 5/6] Add observations button to images pages --- app/views/events/images-automatic.html | 59 ++++-------------- .../events/images-before-mammography.html | 60 ++++--------------- app/views/events/images-manual.html | 56 ++--------------- 3 files changed, 26 insertions(+), 149 deletions(-) diff --git a/app/views/events/images-automatic.html b/app/views/events/images-automatic.html index 6594f6ee..822ee615 100644 --- a/app/views/events/images-automatic.html +++ b/app/views/events/images-automatic.html @@ -48,43 +48,20 @@ {% block pageContent %} - {% set rawBreastFeatures = event.medicalInformation.breastFeaturesRaw | parseJsonString %} - {% set breastFeatures = event.medicalInformation.breastFeatures or rawBreastFeatures %} - {% set reviewAfterImagingStatus = data.event.workflowStatus['review-breast-features-after-imaging'] %} - {% if not reviewAfterImagingStatus %} - {% set reviewAfterImagingStatus = event.workflowStatus['review-breast-features-after-imaging'] %} - {% endif %} -

{{ pageHeading }}

- {% set allowEdits = true %} -

Are there any breast features to add?

-
- {# -

- Click the diagram to add notable features such as scars, moles, or warts that image readers may need to be aware of. These could be observed by you or reported by the participant. -

-
-

Where is the feature?

-
- #} -
-
- {% include "_includes/breast-diagram-wrapper.njk" %} -
- {% include "_includes/feature-popover.njk" %} - {% include "_includes/features-list.njk" %} +
+
+

Observations during mammogram

+ {{ button({ + text: "Add breast features", + href: eventUrl + "/medical-information/record-breast-features" | urlWithReferrer(currentUrl), + variant: "secondary", + small: true + }) }} +
- {{ button({ - text: "Save", - type: "button", - variant: "secondary", - attributes: { - id: "saveBtn" - } - }) }} - {% if data.event.workflowStatus["take-images"] == 'completed' %} @@ -158,22 +135,6 @@

Additional details

{% endblock %} {% block pageScripts %} - - - - - - {% set insetHtml %}

Mammography images will appear below once they have been received diff --git a/app/views/events/images-manual.html b/app/views/events/images-manual.html index e901dacd..7406a108 100644 --- a/app/views/events/images-manual.html +++ b/app/views/events/images-manual.html @@ -14,13 +14,6 @@ {% block pageContent %} - {% set rawBreastFeatures = event.medicalInformation.breastFeaturesRaw | parseJsonString %} - {% set breastFeatures = event.medicalInformation.breastFeatures or rawBreastFeatures %} - {% set reviewAfterImagingStatus = data.event.workflowStatus['review-breast-features-after-imaging'] %} - {% if not reviewAfterImagingStatus %} - {% set reviewAfterImagingStatus = event.workflowStatus['review-breast-features-after-imaging'] %} - {% endif %} - {% set mammogramSource = event.mammogramDataTemp or event.mammogramData %} {# Determine current room name #} @@ -59,51 +52,14 @@

{{ pageHeading }}

{% endif %} - {% set allowEdits = true %} -

Are there any breast features to add?

-
- {# -

- Click the diagram to add notable features such as scars, moles, or warts that image readers may need to be aware of. These could be observed by you or reported by the participant. -

-
-

Where is the feature?

-
- #} -
-
- {% include "_includes/breast-diagram-wrapper.njk" %} -
- {% include "_includes/feature-popover.njk" %} - {% include "_includes/features-list.njk" %} -
- +

Observations during mammogram

{{ button({ - text: "Save", - type: "button", + text: "Add breast features", + href: eventUrl + "/medical-information/record-breast-features" | urlWithReferrer(currentUrl), variant: "secondary", - attributes: { - id: "saveBtn" - } + small: true }) }} - - - - {# {{ radios({ name: "event[mammogramDataTemp][machineRoom]", value: mammogramSource.machineRoom, @@ -139,9 +95,9 @@

Where is the feature?

{% set troubleshootingIssue = event.mammogramDataTemp.troubleshootingIssue %} {% set hasTroubleshootingIssue = troubleshootingIssue in ['worklist-participant', 'wrong-image-count', 'incorrect-image-labels'] %} {% set showWorklistMatchingContent = troubleshootingIssue == 'worklist-participant' %} - {% set showDefaultManualContent = data.settings.screening.manualImageCollection == 'true' or data.event.isManualImageCollection or isManualFailover %} + {% set showManualParticipantDetails = showWorklistMatchingContent or isManualFailover %} - {% if (hasTroubleshootingIssue and showWorklistMatchingContent) or (not hasTroubleshootingIssue and showDefaultManualContent) %} + {% if showManualParticipantDetails %}

Manually add participant details

Set up an unscheduled appointment using the following information so mammograms can be assigned correctly:

{{ summaryList({ From 9ce32e232f224c80e78e66a5528c8716662ff152 Mon Sep 17 00:00:00 2001 From: Ed Horsford Date: Tue, 14 Jul 2026 16:50:07 +0100 Subject: [PATCH 6/6] Remove abandoned interstitial review page and tidy duplication The reminder banner on check-information replaced the interstitial page approach, so remove the unreachable page, its routes and the per-route flag handling. Saving breast features now resolves the review-at-imaging reminder in one place. Dedupe section status logic in expandable-sections.js. --- app/assets/javascript/expandable-sections.js | 135 +++++------- .../javascript/expanded-state-tracker.js | 21 +- app/lib/utils/status.js | 8 +- app/routes/events.js | 201 +++--------------- app/views/events/check-information.html | 34 +-- app/views/events/images-manual.html | 4 +- .../review-after-imaging-breast-features.html | 47 ---- 7 files changed, 113 insertions(+), 337 deletions(-) delete mode 100644 app/views/events/review-after-imaging-breast-features.html diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index d1d5bff9..6d5883e1 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -12,52 +12,34 @@ document.addEventListener('DOMContentLoaded', function () { 'input[name="event[workflowStatus][review-breast-features-after-imaging]"]' ) - function getReviewAfterImagingWorkflowStatus() { - if (!reviewAfterImagingField) { - return '' + function setReviewAfterImagingFlag(statusValue) { + if (reviewAfterImagingField) { + reviewAfterImagingField.value = statusValue } - - return reviewAfterImagingField.value || '' } - function setReviewAfterImagingFlag(statusValue) { - if (!reviewAfterImagingField) { - return - } + // Status implied by the review-at-imaging workflow flag, if any + function getReviewAfterImagingSectionStatus() { + const flagValue = reviewAfterImagingField + ? reviewAfterImagingField.value + : '' - reviewAfterImagingField.value = statusValue || '' + if (flagValue === 'yes') { + return REVIEW_AT_IMAGING_STATUS + } + if (flagValue === 'answered') { + return 'Reviewed' + } + return null } - function completeSectionAndContinue(section, index, options = {}) { - const forcedStatus = options.forcedStatus - + function completeSectionAndContinue(section, index, forcedStatus) { // Mark current section as completed completedSections.add(index) - // Determine current status and set new status - const currentStatus = getCurrentSectionStatus(section) - let newStatus - - if (forcedStatus) { - newStatus = forcedStatus - } else if (currentStatus === 'Incomplete') { - newStatus = 'Complete' - } else if (currentStatus === 'To review') { - newStatus = 'Reviewed' - } else if (currentStatus === 'Reviewed') { - newStatus = 'Reviewed' // Keep as reviewed - } else { - newStatus = 'Complete' // Default fallback - } - - // Update the status for this section - updateSectionStatus(section, newStatus) - - // Save status to sessionStorage - const sectionId = section.getAttribute('id') - if (sectionId && window.saveSectionStatus) { - window.saveSectionStatus(sectionId, newStatus) - } + const newStatus = + forcedStatus || getNextStatus(getCurrentSectionStatus(section)) + applySectionStatus(section, newStatus) // Close current section section.removeAttribute('open') @@ -84,20 +66,11 @@ document.addEventListener('DOMContentLoaded', function () { const sectionId = section.getAttribute('id') const isBreastFeaturesSection = sectionId === BREAST_FEATURES_SECTION_ID + // Reflect any review-at-imaging decision in the section's status tag if (isBreastFeaturesSection) { - const reviewAfterImagingWorkflowStatus = - getReviewAfterImagingWorkflowStatus() - - if (reviewAfterImagingWorkflowStatus === 'yes') { - updateSectionStatus(section, REVIEW_AT_IMAGING_STATUS) - if (window.saveSectionStatus) { - window.saveSectionStatus(sectionId, REVIEW_AT_IMAGING_STATUS) - } - } else if (reviewAfterImagingWorkflowStatus === 'answered') { - updateSectionStatus(section, 'Reviewed') - if (window.saveSectionStatus) { - window.saveSectionStatus(sectionId, 'Reviewed') - } + const impliedStatus = getReviewAfterImagingSectionStatus() + if (impliedStatus) { + applySectionStatus(section, impliedStatus) } } @@ -124,9 +97,7 @@ document.addEventListener('DOMContentLoaded', function () { reviewAfterImagingButton.addEventListener('click', function () { setReviewAfterImagingFlag('yes') - completeSectionAndContinue(section, index, { - forcedStatus: REVIEW_AT_IMAGING_STATUS - }) + completeSectionAndContinue(section, index, REVIEW_AT_IMAGING_STATUS) updateButtonText(section, button) }) } @@ -167,40 +138,18 @@ document.addEventListener('DOMContentLoaded', function () { // Add to completed set completedSections.add(index) - // Determine current status and set new status - const currentStatus = getCurrentSectionStatus(section) - let newStatus - const reviewAfterImagingWorkflowStatus = - getReviewAfterImagingWorkflowStatus() - - if ( - isBreastFeaturesSection && - reviewAfterImagingWorkflowStatus === 'yes' - ) { - newStatus = REVIEW_AT_IMAGING_STATUS - } else if ( - isBreastFeaturesSection && - reviewAfterImagingWorkflowStatus === 'answered' - ) { - newStatus = 'Reviewed' - } else if (currentStatus === 'Incomplete') { - newStatus = 'Complete' - } else if (currentStatus === 'To review') { - newStatus = 'Reviewed' - } else if (currentStatus === 'Reviewed') { - newStatus = 'Reviewed' // Keep as reviewed - } else { - newStatus = 'Complete' // Default fallback + // The breast features section keeps any review-at-imaging status + // rather than being marked reviewed with the rest + let newStatus = null + if (isBreastFeaturesSection) { + newStatus = getReviewAfterImagingSectionStatus() } - - // Update the status for this section - updateSectionStatus(section, newStatus) - - // Save status to sessionStorage - if (sectionId && window.saveSectionStatus) { - window.saveSectionStatus(sectionId, newStatus) + if (!newStatus) { + newStatus = getNextStatus(getCurrentSectionStatus(section)) } + applySectionStatus(section, newStatus) + // Close the section section.removeAttribute('open') }) @@ -277,6 +226,24 @@ function getCurrentSectionStatus(section) { return statusElement ? statusElement.textContent.trim() : 'Incomplete' } +// Work out the status a section moves to when marked as done +function getNextStatus(currentStatus) { + if (currentStatus === 'To review' || currentStatus === 'Reviewed') { + return 'Reviewed' + } + return 'Complete' +} + +// Update a section's status tag and persist it to sessionStorage +function applySectionStatus(section, statusText) { + updateSectionStatus(section, statusText) + + const sectionId = section.getAttribute('id') + if (sectionId && window.saveSectionStatus) { + window.saveSectionStatus(sectionId, statusText) + } +} + // Function to update button text based on section status function updateButtonText(section, button) { const currentStatus = getCurrentSectionStatus(section) diff --git a/app/assets/javascript/expanded-state-tracker.js b/app/assets/javascript/expanded-state-tracker.js index 85c507ce..6c676532 100644 --- a/app/assets/javascript/expanded-state-tracker.js +++ b/app/assets/javascript/expanded-state-tracker.js @@ -10,8 +10,6 @@ const STORAGE_KEY_PREFIX = 'expanded-sections-' const PENDING_EXPAND_KEY_PREFIX = 'pending-expand-' const STATUS_KEY_PREFIX = 'section-statuses-' - const CONFIRM_AFTER_IMAGING_STATUS = 'Review at imaging' - const LEGACY_CONFIRM_AFTER_IMAGING_STATUS = 'Confirm after imaging' function getStorageKey() { return STORAGE_KEY_PREFIX + window.location.pathname @@ -133,12 +131,7 @@ } if (statusElement) { - const normalisedStatusText = - statusText === LEGACY_CONFIRM_AFTER_IMAGING_STATUS - ? CONFIRM_AFTER_IMAGING_STATUS - : statusText - - statusElement.textContent = normalisedStatusText + statusElement.textContent = statusText // Update the tag colour class based on status statusElement.classList.remove( @@ -147,13 +140,15 @@ 'nhsuk-tag--yellow' ) - if (normalisedStatusText === 'Complete' || normalisedStatusText === 'Reviewed') { - statusElement.classList.add('nhsuk-tag--green') - } else if (normalisedStatusText === CONFIRM_AFTER_IMAGING_STATUS) { + if ( + statusText === 'Complete' || + statusText === 'Reviewed' || + statusText === 'Review at imaging' + ) { statusElement.classList.add('nhsuk-tag--green') - } else if (normalisedStatusText === 'Incomplete') { + } else if (statusText === 'Incomplete') { statusElement.classList.add('nhsuk-tag--blue') - } else if (normalisedStatusText === 'To review') { + } else if (statusText === 'To review') { statusElement.classList.add('nhsuk-tag--yellow') } } diff --git a/app/lib/utils/status.js b/app/lib/utils/status.js index 309fdbc3..6937927e 100644 --- a/app/lib/utils/status.js +++ b/app/lib/utils/status.js @@ -223,8 +223,6 @@ const getStatusTagColour = (status) => { 'incomplete': 'blue', 'complete': 'green', 'to_review': 'blue', - 'review_after_imaging': 'green', - 'confirm_after_imaging': 'green', 'reviewed': 'green', // Image reading @@ -329,11 +327,7 @@ const getStatusText = (status) => { prior_requested: 'Requested', prior_received: 'Received', prior_not_available: 'Not available', - prior_not_needed: 'Not needed', - - // Task list statuses - review_after_imaging: 'Review at imaging', - confirm_after_imaging: 'Review at imaging' + prior_not_needed: 'Not needed' // "technical-recall": 'Technical recall', // "recall-for-assesment": 'Recall for assessment', diff --git a/app/routes/events.js b/app/routes/events.js index 9fc97c9b..8c552c17 100644 --- a/app/routes/events.js +++ b/app/routes/events.js @@ -102,10 +102,6 @@ function captureSessionEndTime(data, eventId, userId) { // } module.exports = (router) => { - const getPostImagingDestinationUrl = (data, clinicId, eventId) => { - return `/clinics/${clinicId}/events/${eventId}/check-information` - } - // Set clinics to active in nav for all urls starting with /clinics router.use('/clinics/:clinicId/events/:eventId', (req, res, next) => { const eventId = req.params.eventId @@ -1452,50 +1448,6 @@ module.exports = (router) => { ) // Save breast features (includes converting JSON string to structured data) - const persistBreastFeaturesFromRaw = (data) => { - let errorCount = 0 - let hasBreastFeatures = false - - if (!data?.event) { - return { - errorCount, - hasBreastFeatures - } - } - - if (!data.event.medicalInformation) { - data.event.medicalInformation = {} - } - - const medicalInformation = data.event.medicalInformation - - if (medicalInformation.breastFeaturesRaw) { - try { - const rawFeatures = medicalInformation.breastFeaturesRaw - if (typeof rawFeatures === 'string') { - medicalInformation.breastFeatures = JSON.parse(rawFeatures) - delete medicalInformation.breastFeaturesRaw - console.log( - 'Converted breastFeaturesRaw to structured data and deleted raw data' - ) - } - } catch (error) { - console.warn('Failed to convert breastFeaturesRaw:', error) - errorCount++ - } - } - - const breastFeatures = medicalInformation.breastFeatures - if (Array.isArray(breastFeatures) && breastFeatures.length > 0) { - hasBreastFeatures = true - } - - return { - errorCount, - hasBreastFeatures - } - } - router.post( '/clinics/:clinicId/events/:eventId/medical-information/record-breast-features/save', (req, res) => { @@ -1507,10 +1459,33 @@ module.exports = (router) => { let conversionsCount = 0 let errorCount = 0 - const conversionResult = persistBreastFeaturesFromRaw(data) - errorCount = conversionResult.errorCount - if (conversionResult.hasBreastFeatures) { - conversionsCount++ + // Convert breast features raw data + if (data.event?.medicalInformation?.breastFeaturesRaw) { + try { + const rawFeatures = data.event.medicalInformation.breastFeaturesRaw + if (typeof rawFeatures === 'string') { + data.event.medicalInformation.breastFeatures = + JSON.parse(rawFeatures) + // Delete the raw data once converted + delete data.event.medicalInformation.breastFeaturesRaw + conversionsCount++ + console.log( + 'Converted breastFeaturesRaw to structured data and deleted raw data' + ) + } + } catch (error) { + console.warn('Failed to convert breastFeaturesRaw:', error) + errorCount++ + } + } + + // Saving breast features resolves any 'review at imaging' reminder + if ( + data.event?.workflowStatus?.['review-breast-features-after-imaging'] === + 'yes' + ) { + data.event.workflowStatus['review-breast-features-after-imaging'] = + 'answered' } // Flash error message if needed @@ -1527,16 +1502,6 @@ module.exports = (router) => { referrerChain, scrollTo ) - - if (!data.event.workflowStatus) { - data.event.workflowStatus = {} - } - - if (req.query.fromPostImagingBreastFeatures === '1') { - data.event.workflowStatus['review-breast-features-after-imaging'] = - 'answered' - } - res.redirect(modalBreakout(returnUrl)) } ) @@ -2201,57 +2166,6 @@ module.exports = (router) => { } ) - router.get( - '/clinics/:clinicId/events/:eventId/review-after-imaging-breast-features', - (req, res) => { - res.render('events/review-after-imaging-breast-features') - } - ) - - router.post( - '/clinics/:clinicId/events/:eventId/review-after-imaging-breast-features-answer', - (req, res) => { - const { clinicId, eventId } = req.params - const data = req.session.data - const choice = data?.event?.breastFeaturesAfterImagingDecision - - if (!choice) { - req.flash('error', { - text: 'Select whether to record breast features', - name: 'event[breastFeaturesAfterImagingDecision]' - }) - - return res.redirect( - `/clinics/${clinicId}/events/${eventId}/review-after-imaging-breast-features` - ) - } - - if (choice === 'yes') { - if (!data.event.workflowStatus) { - data.event.workflowStatus = {} - } - - data.event.workflowStatus['review-breast-features-after-imaging'] = - 'answered' - - return res.redirect( - urlWithReferrer( - `/clinics/${clinicId}/events/${eventId}/medical-information/record-breast-features?fromPostImagingBreastFeatures=1`, - `/clinics/${clinicId}/events/${eventId}/check-information` - ) - ) - } - - if (!data.event.workflowStatus) { - data.event.workflowStatus = {} - } - - data.event.workflowStatus['review-breast-features-after-imaging'] = - 'answered' - res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) - } - ) - // Handle screening completion router.post( '/clinics/:clinicId/events/:eventId/imaging-answer', @@ -2289,16 +2203,6 @@ module.exports = (router) => { } if (hasMultipleImages) { - const reviewAfterImagingSelected = - data.event?.workflowStatus?.['review-breast-features-after-imaging'] === - 'yes' - const conversionResult = persistBreastFeaturesFromRaw(data) - - if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { - data.event.workflowStatus['review-breast-features-after-imaging'] = - 'answered' - } - return res.redirect( `/clinics/${clinicId}/events/${eventId}/images-repeats` ) @@ -2310,17 +2214,7 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' - const reviewAfterImagingSelected = - data.event?.workflowStatus?.['review-breast-features-after-imaging'] === - 'yes' - const conversionResult = persistBreastFeaturesFromRaw(data) - - if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { - data.event.workflowStatus['review-breast-features-after-imaging'] = - 'answered' - } - - res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) + res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) } ) @@ -2898,19 +2792,10 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' - const reviewAfterImagingSelected = - data.event?.workflowStatus?.[ - 'review-breast-features-after-imaging' - ] === 'yes' - const conversionResult = persistBreastFeaturesFromRaw(data) - - if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { - data.event.workflowStatus['review-breast-features-after-imaging'] = - 'answered' - } - // Redirect to check information - return res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) + return res.redirect( + `/clinics/${clinicId}/events/${eventId}/check-information` + ) } // If custom details needed, go to details page @@ -2996,18 +2881,8 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' - const reviewAfterImagingSelected = - data.event?.workflowStatus?.['review-breast-features-after-imaging'] === - 'yes' - const conversionResult = persistBreastFeaturesFromRaw(data) - - if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { - data.event.workflowStatus['review-breast-features-after-imaging'] = - 'answered' - } - // Redirect to check information - res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) + res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) } ) @@ -3168,18 +3043,8 @@ module.exports = (router) => { } data.event.workflowStatus['take-images'] = 'completed' - const reviewAfterImagingSelected = - data.event?.workflowStatus?.['review-breast-features-after-imaging'] === - 'yes' - const conversionResult = persistBreastFeaturesFromRaw(data) - - if (reviewAfterImagingSelected && conversionResult.hasBreastFeatures) { - data.event.workflowStatus['review-breast-features-after-imaging'] = - 'answered' - } - // Redirect to check information - res.redirect(getPostImagingDestinationUrl(data, clinicId, eventId)) + res.redirect(`/clinics/${clinicId}/events/${eventId}/check-information`) } ) diff --git a/app/views/events/check-information.html b/app/views/events/check-information.html index 8f4ad9dd..73f659a9 100644 --- a/app/views/events/check-information.html +++ b/app/views/events/check-information.html @@ -70,14 +70,15 @@ {% include "_includes/summary-lists/medical-info-summary.njk" %} {% endset %} -
- {{ card({ - heading: "Medical information", - headingLevel: "2", - feature: true, - descriptionHtml: medicalInfoSummaryHtml - }) }} -
+ {{ card({ + heading: "Medical information", + headingLevel: "2", + feature: true, + descriptionHtml: medicalInfoSummaryHtml, + attributes: { + id: "check-information-breast-features" + } + }) }} {% endset %} @@ -133,14 +134,15 @@ {% include "_includes/medical-information/breast-features.njk" %} {% endset %} -
- {{ card({ - heading: "Breast features", - headingLevel: "2", - feature: true, - descriptionHtml: breastFeaturesHtml - }) }} -
+ {{ card({ + heading: "Breast features", + headingLevel: "2", + feature: true, + descriptionHtml: breastFeaturesHtml, + attributes: { + id: "check-information-breast-features" + } + }) }} {# Other relevant information card #} {% set otherRelevantInformationHtml %} diff --git a/app/views/events/images-manual.html b/app/views/events/images-manual.html index 7406a108..97369e88 100644 --- a/app/views/events/images-manual.html +++ b/app/views/events/images-manual.html @@ -14,6 +14,8 @@ {% block pageContent %} +

{{ pageHeading }}

+ {% set mammogramSource = event.mammogramDataTemp or event.mammogramData %} {# Determine current room name #} @@ -32,8 +34,6 @@ {% endfor %} {% endif %} -

{{ pageHeading }}

- {% if currentRoomName %}

Mammogram location: {{ currentRoomName }}. diff --git a/app/views/events/review-after-imaging-breast-features.html b/app/views/events/review-after-imaging-breast-features.html deleted file mode 100644 index 755ae10d..00000000 --- a/app/views/events/review-after-imaging-breast-features.html +++ /dev/null @@ -1,47 +0,0 @@ -{# app/views/events/review-after-imaging-breast-features.html #} - -{% extends parentLayout or 'layout-appointment.html' %} - -{% set pageHeading = "Review breast features" %} -{% set showNavigation = true %} -{% set activeWorkflowStep = 'check-information' %} -{% set hideBackLink = true %} -{% set gridColumn = "nhsuk-grid-column-two-thirds" %} - -{% set formAction = './review-after-imaging-breast-features-answer' %} - -{% block pageContent %} - -

- {{ pageHeading }} -

- - {{ radios({ - name: "event[breastFeaturesAfterImagingDecision]", - value: event.breastFeaturesAfterImagingDecision, - fieldset: { - legend: { - text: "Are there any breast features to add for " + (participant | getFullName) + "?", - classes: "nhsuk-fieldset__legend--m" - } - }, - hint: { - text: "This includes non-surgical scars, moles and other features that may appear on mammogram images" - }, - items: [ - { - value: "yes", - text: "Yes" - }, - { - value: "no", - text: "No" - } - ] - } | populateErrors) }} - - {{ button({ - text: "Continue" - }) }} - -{% endblock %}