From d2e9eaa988f696c1388ee670832f46e006afab31 Mon Sep 17 00:00:00 2001 From: Ed Horsford Date: Tue, 19 Nov 2024 15:33:25 +0000 Subject: [PATCH] Add first pass at pre-appointment questionnaire --- app/filters.js | 40 +-- app/filters/formatting.js | 35 ++ app/lib/generators/participant-generator.js | 168 ++++++++- app/routes/clinics.js | 117 ++++++ app/routes/settings.js | 30 +- .../_includes/summary-lists/questionnaire.njk | 332 ++++++++++++++++++ app/views/_templates/layout-app.html | 2 +- app/views/_templates/layout.html | 4 + .../questionnaire/current-symptoms.html | 265 ++++++++++++++ .../questionnaire/health-status.html | 180 ++++++++++ .../questionnaire/medical-history.html | 332 ++++++++++++++++++ .../participants/questionnaire/summary.html | 45 +++ app/views/participants/show.html | 181 +++++++++- 13 files changed, 1680 insertions(+), 51 deletions(-) create mode 100644 app/filters/formatting.js create mode 100644 app/views/_includes/summary-lists/questionnaire.njk create mode 100755 app/views/participants/questionnaire/current-symptoms.html create mode 100755 app/views/participants/questionnaire/health-status.html create mode 100755 app/views/participants/questionnaire/medical-history.html create mode 100755 app/views/participants/questionnaire/summary.html diff --git a/app/filters.js b/app/filters.js index 56faf20..b65e0c8 100644 --- a/app/filters.js +++ b/app/filters.js @@ -12,30 +12,30 @@ module.exports = function (env) { /* eslint-disable-line func-names,no-unused-va // Get all files from utils directory const utilsPath = path.join(__dirname, 'lib/utils'); + const filtersPath = path.join(__dirname, 'filters'); + + const folderPaths = [utilsPath, filtersPath] try { - // Read all files in the utils directory - const files = fs.readdirSync(utilsPath); - - files.forEach(file => { - // Only process .js files - if (path.extname(file) === '.js') { - // Get the utils module - const utils = require(path.join(utilsPath, file)); - - // Add each exported function as a filter - Object.entries(utils).forEach(([name, func]) => { - // Only add if it's a function - if (typeof func === 'function') { - filters[name] = func; - } - }); - } + folderPaths.forEach(folderPath => { + const files = fs.readdirSync(folderPath); + + files.forEach(file => { + if (path.extname(file) === '.js') { + const module = require(path.join(folderPath, file)); + + Object.entries(module).forEach(([name, func]) => { + if (typeof func === 'function') { + filters[name] = func; + } + }); + } + }); }); - } catch (err) { - console.warn('Error loading filters from utils:', err); + } + catch (err) { + console.warn('Error loading filters:', err); } - return filters; }; diff --git a/app/filters/formatting.js b/app/filters/formatting.js new file mode 100644 index 0000000..27c4089 --- /dev/null +++ b/app/filters/formatting.js @@ -0,0 +1,35 @@ +/** + * Format a yes/no/not answered response with optional additional details + * @param {string|boolean} value - The response value to format + * @param {Object} [options] - Formatting options + * @param {string} [options.yesValue] - Additional details to show after "Yes -" for positive responses + * @param {string} [options.noText="No"] - Text to show for negative responses + * @param {string} [options.notAnsweredText="Not answered"] - Text to show when no response given + * @param {string} [options.yesPrefix="Yes"] - Text to show for positive responses (before any yesValue) + * @returns {string} Formatted response text + * @example + * formatAnswer("yes", { yesValue: "Details here" }) // Returns "Yes - Details here" + * formatAnswer("no") // Returns "No" + * formatAnswer(null) // Returns "Not answered" + * formatAnswer("yes", { yesPrefix: "Currently" }) // Returns "Currently" + */ +const formatAnswer = (value, options = {}) => { + const { + yesValue = null, + noText = "No", + notAnsweredText = "Not answered", + yesPrefix = "Yes" + } = options; + + // Handle null/undefined/empty string + if (!value || value === "no" || value === "false") { + return noText; + } + + // For any truthy value (includes "yes", true, etc) + return yesValue ? `${yesPrefix} - ${yesValue}` : yesPrefix; +}; + +module.exports = { + formatAnswer +}; diff --git a/app/lib/generators/participant-generator.js b/app/lib/generators/participant-generator.js index 2a2015d..9cda053 100644 --- a/app/lib/generators/participant-generator.js +++ b/app/lib/generators/participant-generator.js @@ -63,6 +63,103 @@ const generateNHSNumber = () => { return `${baseNumber}${finalCheckDigit}`; }; +// New medical history generators +const generateMedicalHistorySurvey = () => { + // 50% chance of having completed the survey + if (Math.random() > 0.5) { + return null; + } + + return { + completedAt: faker.date.past({ years: 1 }).toISOString(), + + // Non-cancerous procedures/diagnoses + nonCancerousProcedures: generateNonCancerousProcedures(), + + // Current hormone therapy (if they had previous cancer) + onHormoneTherapy: Math.random() < 0.3, // 30% of those with cancer history + + // Other medical history + otherMedicalHistory: Math.random() < 0.3 ? + faker.helpers.arrayElements([ + "Type 2 diabetes - diet controlled", + "High blood pressure - medication", + "Osteoarthritis", + "Previous shoulder surgery", + "Rheumatoid arthritis" + ], { min: 1, max: 2 }) : + null + }; +}; + +const generateNonCancerousProcedures = () => { + const procedures = []; + + const possibleProcedures = { + 'benign_lump': { + probability: 0.15, + details: () => ({ + dateDiscovered: faker.date.past({ years: 5 }).toISOString(), + position: faker.helpers.arrayElement(['Left breast', 'Right breast']), + wasRemoved: Math.random() < 0.7, + pathology: 'Fibroadenoma' + }) + }, + 'cyst_aspiration': { + probability: 0.1, + details: () => ({ + date: faker.date.past({ years: 3 }).toISOString(), + location: faker.helpers.arrayElement(['Left breast', 'Right breast']), + notes: 'Simple cyst, fluid aspirated' + }) + }, + 'non_implant_augmentation': { + probability: 0.02, + details: () => ({ + date: faker.date.past({ years: 5 }).toISOString(), + procedure: 'Fat transfer procedure', + hospital: 'General Hospital' + }) + }, + 'breast_reduction': { + probability: 0.03, + details: () => ({ + date: faker.date.past({ years: 5 }).toISOString(), + notes: 'Bilateral breast reduction', + hospital: 'City Hospital' + }) + }, + 'previous_biopsy': { + probability: 0.08, + details: () => ({ + date: faker.date.past({ years: 2 }).toISOString(), + result: 'Benign', + location: faker.helpers.arrayElement(['Left breast', 'Right breast']) + }) + }, + 'skin_lesion': { + probability: 0.05, + details: () => ({ + date: faker.date.past({ years: 3 }).toISOString(), + type: faker.helpers.arrayElement(['Seborrheic keratosis', 'Dermatofibroma']), + location: faker.helpers.arrayElement(['Left breast', 'Right breast']) + }) + } + }; + + Object.entries(possibleProcedures).forEach(([type, config]) => { + if (Math.random() < config.probability) { + procedures.push({ + type, + ...config.details() + }); + } + }); + + return procedures; +}; + + const generateParticipant = ({ ethnicities, breastScreeningUnits }) => { const id = generateId(); @@ -101,7 +198,8 @@ const generateParticipant = ({ ethnicities, breastScreeningUnits }) => { nhsNumber: generateNHSNumber(), riskFactors: generateRiskFactors(), familyHistory: generateFamilyHistory(), - previousCancerHistory: generatePreviousCancerHistory() + previousCancerHistory: generatePreviousCancerHistory(), + medicalHistorySurvey: generateMedicalHistorySurvey() }, currentHealthInformation: { isPregnant: false, @@ -114,6 +212,31 @@ const generateParticipant = ({ ethnicities, breastScreeningUnits }) => { }; +// Modified family history generator to add more detail +const generateFamilyHistory = () => { + if (Math.random() > 0.15) return null; // 15% chance of family history + + const affectedRelatives = faker.helpers.arrayElements( + [ + { relation: 'mother', age: faker.number.int({ min: 35, max: 75 }) }, + { relation: 'sister', age: faker.number.int({ min: 30, max: 70 }) }, + { relation: 'daughter', age: faker.number.int({ min: 25, max: 45 }) }, + { relation: 'grandmother', age: faker.number.int({ min: 45, max: 85 }) }, + { relation: 'aunt', age: faker.number.int({ min: 40, max: 80 }) } + ], + { min: 1, max: 3 } + ); + + return { + hasFirstDegreeHistory: affectedRelatives.some(r => + ['mother', 'sister', 'daughter'].includes(r.relation) + ), + affectedRelatives, + additionalDetails: Math.random() < 0.3 ? + 'Multiple occurrences on maternal side' : null + }; +}; + const generateRiskFactors = () => { const factors = []; const possibleFactors = { @@ -133,21 +256,21 @@ const generateRiskFactors = () => { return factors; }; -const generateFamilyHistory = () => { - if (Math.random() > 0.15) return null; // 15% chance of family history - - return { - hasFirstDegreeHistory: Math.random() < 0.7, // 70% of those with family history - affectedRelatives: faker.helpers.arrayElements( - ['mother', 'sister', 'daughter', 'grandmother', 'aunt'], - { min: 1, max: 3 } - ) - }; -}; +// Modified previous cancer history to include more detail const generatePreviousCancerHistory = () => { if (Math.random() > 0.02) return null; // 2% chance of previous cancer + const treatments = faker.helpers.arrayElements( + [ + { type: 'surgery', details: 'Wide local excision' }, + { type: 'radiotherapy', details: '15 fractions' }, + { type: 'chemotherapy', details: '6 cycles' }, + { type: 'hormone_therapy', details: '5 years tamoxifen' } + ], + { min: 1, max: 3 } + ); + return { yearDiagnosed: faker.date.past({ years: 20 }).getFullYear(), type: faker.helpers.arrayElement([ @@ -155,12 +278,21 @@ const generatePreviousCancerHistory = () => { 'invasive_ductal_carcinoma', 'invasive_lobular_carcinoma' ]), - treatment: faker.helpers.arrayElements([ - 'surgery', - 'radiotherapy', - 'chemotherapy', - 'hormone_therapy' - ], { min: 1, max: 3 }) + position: faker.helpers.arrayElement([ + 'Left breast - upper outer quadrant', + 'Right breast - upper outer quadrant', + 'Left breast - lower inner quadrant', + 'Right breast - lower inner quadrant' + ]), + treatments, + hospital: faker.helpers.arrayElement([ + 'City General Hospital', + 'Royal County Hospital', + 'Memorial Cancer Centre', + 'University Teaching Hospital' + ]), + additionalNotes: Math.random() < 0.3 ? + 'Regular follow-up completed' : null }; }; diff --git a/app/routes/clinics.js b/app/routes/clinics.js index e61ef55..646e3f4 100644 --- a/app/routes/clinics.js +++ b/app/routes/clinics.js @@ -102,6 +102,123 @@ module.exports = router => { }); }); + // Helper to validate section name + const isValidSection = (section) => QUESTIONNAIRE_SECTIONS.includes(section); + + const QUESTIONNAIRE_SECTIONS = ['health-status', 'medical-history', 'current-symptoms']; + + // Helper to get next section + const getNextSection = (currentSection) => { + const currentIndex = QUESTIONNAIRE_SECTIONS.indexOf(currentSection); + return QUESTIONNAIRE_SECTIONS[currentIndex + 1]; + }; + + // Optional: Add a summary view + router.get('/clinics/:clinicId/participants/:participantId/questionnaire/summary', (req, res) => { + const { clinicId, participantId } = req.params; + + const participant = req.session.data.participants.find(p => p.id === participantId); + const clinic = req.session.data.clinics.find(c => c.id === clinicId); + const event = req.session.data.events.find(e => + e.clinicId === clinicId && + e.participantId === participantId + ); + + if (!participant || !clinic || !event) { + res.redirect('/clinics/' + clinicId); + return; + } + + // Collect all questionnaire data + const questionnaireData = QUESTIONNAIRE_SECTIONS.reduce((acc, section) => { + acc[section] = req.session.data[`questionnaire_${section}`] || {}; + return acc; + }, {}); + + res.render('participants/questionnaire/summary', { + participant, + clinic, + event, + clinicId, + participantId, + questionnaireData + }); + }); + + // Base questionnaire route + router.get('/clinics/:clinicId/participants/:participantId/questionnaire/:section', (req, res) => { + const { clinicId, participantId, section } = req.params; + + // Validate section name + if (!QUESTIONNAIRE_SECTIONS.includes(section)) { + res.redirect(`/clinics/${clinicId}/participants/${participantId}/questionnaire/health-status`); + return; + } + + const participant = req.session.data.participants.find(p => p.id === participantId); + const clinic = req.session.data.clinics.find(c => c.id === clinicId); + const event = req.session.data.events.find(e => + e.clinicId === clinicId && + e.participantId === participantId + ); + + if (!participant || !clinic || !event) { + res.redirect('/clinics/' + clinicId); + return; + } + + res.render(`participants/questionnaire/${section}`, { + participant, + clinic, + event, + clinicId, + participantId, + currentSection: section, + sections: QUESTIONNAIRE_SECTIONS + }); + }); + + // After summary confirmation, we could save back to participant record + router.post('/clinics/:clinicId/participants/:participantId/questionnaire/complete', (req, res) => { + const { clinicId, participantId } = req.params; + + // Find participant + const participantIndex = req.session.data.participants.findIndex(p => p.id === participantId); + + if (participantIndex !== -1) { + // Update participant record with questionnaire data + req.session.data.participants[participantIndex] = { + ...req.session.data.participants[participantIndex], + questionnaire: req.session.data.questionnaire + }; + } + + // Clear questionnaire data from session + delete req.session.data.questionnaire; + + res.redirect(`/clinics/${clinicId}/participants/${participantId}`); + }); + + // Handle form submissions + router.post('/clinics/:clinicId/participants/:participantId/questionnaire/:section', (req, res) => { + const { clinicId, participantId, section } = req.params; + + // Get next section + const nextSection = getNextSection(section); + + if (nextSection) { + res.redirect(`/clinics/${clinicId}/participants/${participantId}/questionnaire/${nextSection}`); + } else { + res.redirect(`/clinics/${clinicId}/participants/${participantId}/questionnaire/summary`); + } + }); + + // Add a convenience redirect from the base questionnaire URL to the first section + router.get('/clinics/:clinicId/participants/:participantId/questionnaire', (req, res) => { + const { clinicId, participantId } = req.params; + res.redirect(`/clinics/${clinicId}/participants/${participantId}/questionnaire/health-status`); + }); + // Handle check-in router.get('/clinics/:clinicId/check-in/:eventId', (req, res) => { const { clinicId, eventId } = req.params; diff --git a/app/routes/settings.js b/app/routes/settings.js index d435f8c..5cf6f08 100644 --- a/app/routes/settings.js +++ b/app/routes/settings.js @@ -9,16 +9,24 @@ const { regenerateData } = require('../lib/utils/regenerate-data'); module.exports = router => { -// Handle regenerate data action -router.post('/settings/regenerate', async (req, res) => { - try { - await regenerateData(req); - req.flash('success', 'Data regenerated successfully'); - } catch (err) { - console.error('Error regenerating data:', err); - req.flash('error', 'Error regenerating data'); - } - res.redirect('/settings'); -}); + // Handle regenerate data action + router.post('/settings/regenerate', async (req, res) => { + try { + await regenerateData(req); + req.flash('success', 'Data regenerated successfully'); + } catch (err) { + console.error('Error regenerating data:', err); + req.flash('error', 'Error regenerating data'); + } + res.redirect('/settings'); + }); + + // Handle regenerate data action + router.get('/clear-data', async (req, res) => { + console.log("Clearing session data") + req.session.data = {} + req.flash('success', 'Session data cleared'); + res.redirect('/settings'); + }); } diff --git a/app/views/_includes/summary-lists/questionnaire.njk b/app/views/_includes/summary-lists/questionnaire.njk new file mode 100644 index 0000000..9f3ce12 --- /dev/null +++ b/app/views/_includes/summary-lists/questionnaire.njk @@ -0,0 +1,332 @@ +

Health status

+ + + + {% set mobilityValue %} + {{ data.questionnaire.mobilityIssues | formatAnswer({ yesValue: data.questionnaire.mobilityYes }) }} + {% endset %} + + {% set medicalDevicesValue %} + {% if data.questionnaire.medicalDevices === "yes" and data.questionnaire.medicalDeviceTypes %} + + {% else %} + {{ data.questionnaire.medicalDevices | formatAnswer }} + {% endif %} + {% endset %} + + {{ summaryList({ + rows: [ + { + key: { + text: "Mobility issues" + }, + value: { + html: mobilityValue + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/health-status#mobilityIssues", + text: "Change", + visuallyHiddenText: "mobility issues" + } + ] + } + }, + { + key: { + text: "Pregnant or breastfeeding" + }, + value: { + text: data.questionnaire.pregnantOrBreastFeeding | formatAnswer + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/health-status#pregnantOrBreastFeeding", + text: "Change", + visuallyHiddenText: "pregnancy status" + } + ] + } + }, + { + key: { + text: "Medical devices" + }, + value: { + html: medicalDevicesValue + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/health-status#medicalDevices", + text: "Change", + visuallyHiddenText: "medical devices" + } + ] + } + }, + { + key: { + text: "Taking HRT" + }, + value: { + text: data.questionnaire.takingHRT | formatAnswer({ yesValue: data.questionnaire.hrtType }) + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/health-status#takingHRT", + text: "Change", + visuallyHiddenText: "HRT status" + } + ] + } + } + ] + }) }} + +

Medical history

+ + {% set previousCancerValue %} + {% if data.questionnaire.previousCancer === "yes" %} +

Yes

+ + {% else %} + {{ data.questionnaire.previousCancer | formatAnswer }} + {% endif %} + {% endset %} + + {% set previousProceduresValue %} + {% if data.questionnaire.previousProcedures %} + {% if data.questionnaire.previousProcedures.includes("none") %} + None + {% else %} + + {% endif %} + {% else %} + Not answered + {% endif %} + {% endset %} + + {{ summaryList({ + rows: [ + { + key: { + text: "Previous breast cancer" + }, + value: { + html: previousCancerValue + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/medical-history#previousCancer", + text: "Change", + visuallyHiddenText: "previous cancer history" + } + ] + } + }, + { + key: { + text: "Current hormone therapy" + }, + value: { + text: data.questionnaire.hormoneTherapy | formatAnswer + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/medical-history#hormoneTherapy", + text: "Change", + visuallyHiddenText: "hormone therapy status" + } + ] + } + }, + { + key: { + text: "Family history" + }, + value: { + text: data.questionnaire.familyHistory | formatAnswer({ yesValue: data.questionnaire.familyHistoryDetails }) + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/medical-history#familyHistory", + text: "Change", + visuallyHiddenText: "family history" + } + ] + } + }, + { + key: { + text: "Previous procedures" + }, + value: { + html: previousProceduresValue + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/medical-history#previousProcedures", + text: "Change", + visuallyHiddenText: "previous procedures" + } + ] + } + } + ] + }) }} + +

Current symptoms

+ + {% set skinChangesValue %} + {% if data.questionnaire.skinChanges === "yes" and data.questionnaire.skinChangeTypes %} + + {% else %} + {{ data.questionnaire.skinChanges | formatAnswer({ noText: "No changes reported" }) }} + {% endif %} + {% endset %} + + {% set nippleSymptomsValue %} + {% if data.questionnaire.nippleSymptoms %} + {% if data.questionnaire.nippleSymptoms.includes("none") %} + No symptoms reported + {% else %} + + {% if data.questionnaire.nippleSymptomDetails %} +

Additional details: {{data.questionnaire.nippleSymptomDetails}}

+ {% endif %} + {% endif %} + {% else %} + Not answered + {% endif %} + {% endset %} + + {{ summaryList({ + rows: [ + { + key: { + text: "Skin changes" + }, + value: { + html: skinChangesValue + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/current-symptoms#skinChanges", + text: "Change", + visuallyHiddenText: "skin changes" + } + ] + } + }, + { + key: { + text: "Lumps" + }, + value: { + text: data.questionnaire.lumpsNoticed | formatAnswer({ + yesValue: data.questionnaire.lumpDetails, + noText: "No lumps reported" + }) + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/current-symptoms#lumpsNoticed", + text: "Change", + visuallyHiddenText: "lumps" + } + ] + } + }, + { + key: { + text: "Breast shape changes" + }, + value: { + text: data.questionnaire.shapeChanges | formatAnswer({ + yesValue: data.questionnaire.shapeChangeDetails, + noText: "No changes reported" + }) + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/current-symptoms#shapeChanges", + text: "Change", + visuallyHiddenText: "shape changes" + } + ] + } + }, + { + key: { + text: "Nipple symptoms" + }, + value: { + html: nippleSymptomsValue + }, + actions: { + items: [ + { + href: "/clinics/" + clinicId + "/participants/" + participantId + "/questionnaire/current-symptoms#nippleSymptoms", + text: "Change", + visuallyHiddenText: "nipple symptoms" + } + ] + } + } + ] + }) }} diff --git a/app/views/_templates/layout-app.html b/app/views/_templates/layout-app.html index 10d18c0..679e71d 100755 --- a/app/views/_templates/layout-app.html +++ b/app/views/_templates/layout-app.html @@ -82,7 +82,7 @@ {% endif %} {% if formAction or isForm %} -
+ {% endif %} {% block pageContent %}{% endblock %} {% if formAction or isForm %} diff --git a/app/views/_templates/layout.html b/app/views/_templates/layout.html index 0f0f3c6..ef1e19f 100755 --- a/app/views/_templates/layout.html +++ b/app/views/_templates/layout.html @@ -51,6 +51,10 @@ "URL": "/settings", "label": "Settings" }, + { + "URL": "/clear-data", + "label": "Clear data" + }, { "URL": "https://github.com/NHSDigital/manage-screening-events-prototype", "label": "Github" diff --git a/app/views/participants/questionnaire/current-symptoms.html b/app/views/participants/questionnaire/current-symptoms.html new file mode 100755 index 0000000..1d6173f --- /dev/null +++ b/app/views/participants/questionnaire/current-symptoms.html @@ -0,0 +1,265 @@ + +{% extends 'layout.html' %} + +{% set pageHeading = "Current symptoms" %} + +{% set formAction = "./current-symptoms" %} + + +{% set hideBackLink = true %} + +{% block content %} +
+
+ + {% if flash.error %} + {{ errorSummary({ + "titleText": "There is a problem", + "errorList": flash.error + }) }} + {% endif %} + + +

+ {{( participant | getFullName ) or "Jane Smith" }} pre-screening questionnaire + {{ pageHeading }} +

+ + {# Skin changes question #} + {% set skinChangesYesHtml %} + {{ checkboxes({ + name: "questionnaire[skinChangeTypes]", + items: [ + { + value: "rash", + text: "Rash" + }, + { + value: "tethering", + text: "Tethering" + }, + { + value: "dimpling", + text: "Dimpling" + } + ] + }) }} + + {{ textarea({ + name: "questionnaire[skinChangeDetails]", + id: "skinChangeDetails", + label: { + text: "Please provide details of location and how long you’ve noticed these" + } + }) }} + {% endset -%} + + {{ radios({ + idPrefix: "skinChanges", + name: "questionnaire[skinChanges]", + fieldset: { + legend: { + text: "Have you noticed any skin changes on your breasts recently?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: false + } + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: skinChangesYesHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {# Lumps question #} + {% set lumpsYesHtml %} + {{ textarea({ + name: "questionnaire[lumpDetails]", + id: "lumpDetails", + label: { + text: "Please describe the location and how long you've noticed the lump" + }, + hint: { + text: "For example: 'Upper right breast, noticed 2 weeks ago'" + } + }) }} + {% endset -%} + + {{ radios({ + idPrefix: "lumpsNoticed", + name: "questionnaire[lumpsNoticed]", + fieldset: { + legend: { + text: "Have you noticed any lumps in your breasts?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: false + } + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: lumpsYesHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {# Breast shape changes #} + {% set shapeChangesYesHtml %} + {{ textarea({ + name: "questionnaire[shapeChangeDetails]", + id: "shapeChangeDetails", + label: { + text: "Please describe the changes you've noticed and when you first noticed them" + } + }) }} + {% endset -%} + + {{ radios({ + idPrefix: "shapeChanges", + name: "questionnaire[shapeChanges]", + fieldset: { + legend: { + text: "Have you noticed a change in the shape of your breasts recently?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: false + } + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: shapeChangesYesHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {% set nippleSymptomDetailsHtml %} + {{ textarea({ + name: "questionnaire[nippleSymptomDetails]", + id: "nippleSymptomDetails", + label: { + text: "Please provide details of which nipple and how long you've noticed this" + } + }) }} +{% endset -%} + +{{ checkboxes({ + idPrefix: "nippleSymptoms", + name: "questionnaire[nippleSymptoms]", + fieldset: { + legend: { + text: "Have you noticed any changes to your nipples?", + classes: "nhsuk-fieldset__legend--s" + } + }, + hint: { + text: "Select all that apply" + }, + items: [ + { + value: "distortion", + text: "Change in the shape or position" + }, + { + value: "eczema", + text: "Dry, red, itchy or scaly skin" + }, + { + value: "discharge", + text: "Blood-stained discharge" + }, + { + value: "inversion", + text: "Nipple pulling inward or changing direction" + }, + { + divider: "or" + }, + { + value: "none", + text: "No, I have not noticed any changes", + behaviour: "exclusive" + } + ], + conditional: { + html: nippleSymptomDetailsHtml + } +}) }} + + {# Breast scars #} + {% set scarsYesHtml %} + {{ textarea({ + name: "questionnaire[scarLocation]", + id: "scarLocation", + label: { + text: "Where are the scars located?" + } + }) }} + + {{ textarea({ + name: "questionnaire[scarReason]", + id: "scarReason", + label: { + text: "What was the reason for the scarring?" + }, + hint: { + text: "For example: surgery, injury, etc." + } + }) }} + {% endset -%} + + {{ radios({ + idPrefix: "breastScars", + name: "questionnaire[breastScars]", + fieldset: { + legend: { + text: "Do you have any scars on your breasts?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: false + } + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: scarsYesHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {{ button({ + text: "Continue" + }) }} + +
+
+ + +{% endblock %} diff --git a/app/views/participants/questionnaire/health-status.html b/app/views/participants/questionnaire/health-status.html new file mode 100755 index 0000000..489b154 --- /dev/null +++ b/app/views/participants/questionnaire/health-status.html @@ -0,0 +1,180 @@ + +{% extends 'layout.html' %} + +{% set pageHeading = "Health status" %} + +{% set formAction = "./health-status" %} + +{% set hideBackLink = true %} + +{% block content %} +
+
+ + {% if flash.error %} + {{ errorSummary({ + "titleText": "There is a problem", + "errorList": flash.error + }) }} + {% endif %} + +
+

+ {{( participant | getFullName ) or "Jane Smith" }} pre-screening questionnaire + {{ pageHeading }} +

+ + {% set mobilityYesHtml %} + {{ textarea({ + name: "questionnaire[mobilityYes]", + id: "mobilityYes", + label: { + text: "Can you provide more detail?" + }, + _hint: { + text: "Do not include personal or financial information, for example, your National Insurance number or credit card details." + } + }) }} + {% endset -%} + + {{ radios({ + idPrefix: "mobilityIssues", + name: "questionnaire[mobilityIssues]", + fieldset: { + legend: { + text: "Do you have any mobility issues?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: "false" + } + }, + hint: { + text: "We only need to know about things that may affect your visit" + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: mobilityYesHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {{ radios({ + idPrefix: "pregnantOrBreastFeeding", + name: "questionnaire[pregnantOrBreastFeeding]", + fieldset: { + legend: { + text: "Are you pregnant or breast feeding?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: "false" + } + }, + items: [ + { + value: "yes", + text: "Yes, I’m currently pregnant or breastfeeding" + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {# Medical devices question #} + {% set medicalDevicesYesHtml %} + {{ checkboxes({ + name: "questionnaire[medicalDeviceTypes]", + items: [ + { + value: "pacemaker", + text: "Pacemaker" + }, + { + value: "insulin_pump", + text: "Insulin pump" + }, + { + value: "other", + text: "Other implanted device" + } + ] + }) }} + {% endset -%} + + {{ radios({ + idPrefix: "medicalDevices", + name: "questionnaire[medicalDevices]", + fieldset: { + legend: { + text: "Do you have any implanted medical devices or pacemakers?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: false + } + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: medicalDevicesYesHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {# HRT question #} + {% set hrtYesHtml %} + {{ input({ + label: { + text: "What type of HRT are you taking?" + }, + id: "hrtType", + name: "questionnaire[hrtType]" + }) }} + {% endset -%} + + {{ radios({ + idPrefix: "takingHRT", + name: "questionnaire[takingHRT]", + fieldset: { + legend: { + text: "Are you taking HRT (hormone replacement therapy)?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: false + } + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: hrtYesHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {{ button({ + text: "Continue" + }) }} +
+
+
+ + +{% endblock %} diff --git a/app/views/participants/questionnaire/medical-history.html b/app/views/participants/questionnaire/medical-history.html new file mode 100755 index 0000000..8786c15 --- /dev/null +++ b/app/views/participants/questionnaire/medical-history.html @@ -0,0 +1,332 @@ + +{% extends 'layout.html' %} + +{% set pageHeading = "Medical history" %} + +{% set formAction = "./medical-history" %} + + +{% set hideBackLink = true %} + +{% block content %} +{{ data | log }} +
+
+ + {% if flash.error %} + {{ errorSummary({ + "titleText": "There is a problem", + "errorList": flash.error + }) }} + {% endif %} + +
+

+ {{( participant | getFullName ) or "Jane Smith" }} pre-screening questionnaire + {{ pageHeading }} +

+ + {# Previous breast cancer diagnosis #} + {% set previousCancerHtml %} + {{ dateInput({ + id: "diagnosisDate", + namePrefix: "questionnaire[diagnosisDate]", + fieldset: { + legend: { + text: "When were you diagnosed?", + classes: "nhsuk-fieldset__legend--s" + } + }, + hint: { + text: "For example, 15 3 2020" + }, + items: [ + { + name: "day", + classes: "nhsuk-input--width-2" + }, + { + name: "month", + classes: "nhsuk-input--width-2" + }, + { + name: "year", + classes: "nhsuk-input--width-4" + } + ] + }) }} + + {{ input({ + label: { + text: "What was the diagnosis?" + }, + classes: "nhsuk-input--width-20", + id: "diagnosis", + name: "questionnaire[diagnosis]" + }) }} + + {{ input({ + label: { + text: "Where was the cancer located?" + }, + classes: "nhsuk-input--width-20", + id: "position", + name: "questionnaire[position]" + }) }} + + {{ checkboxes({ + name: "questionnaire[treatments]", + fieldset: { + legend: { + text: "What treatments did you receive?", + classes: "nhsuk-fieldset__legend--s" + } + }, + items: [ + { + value: "radiotherapy", + text: "Radiotherapy" + }, + { + value: "mastectomy", + text: "Mastectomy" + }, + { + value: "lumpectomy", + text: "Lumpectomy" + }, + { + value: "other_surgery", + text: "Other surgery" + } + ] + }) }} + + {{ input({ + label: { + text: "Which hospital provided your treatment?" + }, + classes: "nhsuk-input--width-20", + id: "treatmentHospital", + name: "questionnaire[treatmentHospital]" + }) }} + + {% endset -%} + + {{ radios({ + idPrefix: "previousCancer", + name: "questionnaire[previousCancer]", + fieldset: { + legend: { + text: "Have you been diagnosed with breast cancer before?", + classes: "nhsuk-fieldset__legend--s", + isPageHeading: false + } + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: previousCancerHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {# Current hormone therapy #} + {{ radios({ + idPrefix: "hormoneTherapy", + name: "questionnaire[hormoneTherapy]", + fieldset: { + legend: { + text: "Are you currently receiving hormone therapy treatment for breast cancer?", + classes: "nhsuk-fieldset__legend--s" + } + }, + items: [ + { + value: "yes", + text: "Yes" + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {# Family history #} + {% set familyHistoryYesHtml %} + {{ textarea({ + name: "questionnaire[familyHistoryDetails]", + id: "familyHistoryDetails", + label: { + text: "Please tell us about your family history of breast cancer" + } + }) }} + {% endset -%} + + {{ radios({ + idPrefix: "familyHistory", + name: "questionnaire[familyHistory]", + fieldset: { + legend: { + text: "Do you have a family history of breast cancer?", + classes: "nhsuk-fieldset__legend--s" + } + }, + items: [ + { + value: "yes", + text: "Yes", + conditional: { + html: familyHistoryYesHtml + } + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {# Previous non-cancerous procedures #} + {% set benignLumpHtml %} + {{ dateInput({ + id: "benignLumpDate", + namePrefix: "questionnaire[benignLumpDate]", + fieldset: { + legend: { + text: "When was it discovered?", + classes: "nhsuk-fieldset__legend--s" + } + }, + hint: { + text: "For example, 15 3 2020" + }, + items: [ + { + name: "day", + classes: "nhsuk-input--width-2" + }, + { + name: "month", + classes: "nhsuk-input--width-2" + }, + { + name: "year", + classes: "nhsuk-input--width-4" + } + ] + }) }} + + {{ input({ + label: { + text: "Where was it located?" + }, + classes: "nhsuk-input--width-20", + id: "benignLumpPosition", + name: "questionnaire[benignLumpPosition]" + }) }} + + {{ radios({ + idPrefix: "benignLumpExcision", + name: "questionnaire[benignLumpExcision]", + fieldset: { + legend: { + text: "Was it removed?", + classes: "nhsuk-fieldset__legend--s" + } + }, + items: [ + { + value: "yes", + text: "Yes" + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {{ textarea({ + name: "questionnaire[benignLumpOutcome]", + id: "benignLumpOutcome", + label: { + text: "What was the outcome/pathology?" + } + }) }} + {% endset -%} + + {{ checkboxes({ + name: "questionnaire[previousProcedures]", + fieldset: { + legend: { + text: "Have you had any of the following non-cancerous procedures or diagnoses?", + classes: "nhsuk-fieldset__legend--s" + } + }, + hint: { + text: "Select all that apply" + }, + items: [ + { + value: "benign_lump", + text: "Benign lump", + conditional: { + html: benignLumpHtml + } + }, + { + value: "cyst_aspiration", + text: "Cyst aspiration" + }, + { + value: "augmentation", + text: "Non implant based augmentation" + }, + { + value: "other_surgery", + text: "Other breast surgery (reductions/asymmetrical/fat distribution)" + }, + { + value: "biopsies", + text: "Previous biopsies" + }, + { + value: "skin_lesions", + text: "Skin lesions" + }, + { + value: "none", + text: "None of the above", + behaviour: "exclusive" + } + ] + }) }} + + {# Other medical history #} + {{ textarea({ + name: "questionnaire[otherMedicalHistory]", + id: "otherMedicalHistory", + label: { + text: "Do you have any other relevant medical history you'd like to tell us about?", + classes: "nhsuk-label--s" + } + }) }} + + {{ button({ + text: "Continue" + }) }} +
+
+
+ + +{% endblock %} diff --git a/app/views/participants/questionnaire/summary.html b/app/views/participants/questionnaire/summary.html new file mode 100755 index 0000000..6e75b98 --- /dev/null +++ b/app/views/participants/questionnaire/summary.html @@ -0,0 +1,45 @@ + +{% extends 'layout.html' %} + +{% set pageHeading = "Summary" %} + +{% set formAction = "./complete" %} + + +{% set hideBackLink = true %} + +{% block content %} +
+
+ + {% if flash.error %} + {{ errorSummary({ + "titleText": "There is a problem", + "errorList": flash.error + }) }} + {% endif %} + +
+

+ {{( participant | getFullName ) or "Jane Smith" }} pre-screening questionnaire + {{ pageHeading }} +

+ + {% include "summary-lists/questionnaire.njk" %} + + {{ button({ + text: "Confirm and submit" + }) }} + +

+ + Go back and make changes + +

+ +
+
+
+ + +{% endblock %} diff --git a/app/views/participants/show.html b/app/views/participants/show.html index da1ed9b..7a249b0 100644 --- a/app/views/participants/show.html +++ b/app/views/participants/show.html @@ -15,6 +15,8 @@ {% block pageContent %} +{{ participant | log }} + {% set unit = data.breastScreeningUnits | findById(clinic.breastScreeningUnitId) %}

@@ -40,6 +42,7 @@

Appointment

{% endif %} {% endset %} + {% set appointmentHtml %} {{ summaryList({ rows: [ @@ -189,6 +192,32 @@

Appointment

descriptionHtml: personalDetailsHtml }) }} + {% set screeningHistoryHtml %} + + {% set lastMamogramDateHtml %} + {% set lastMamogramDate = "2021-12-27T12:54:27.756Z" %} + {{ lastMamogramDate | formatDate }}
+ ({{ lastMamogramDate | formatRelativeDate }}) + {% endset %} + + {{ summaryList({ + rows: [ + { + key: { text: "Date of last mamogram" }, + value: { + html: lastMamogramDateHtml + } + } + ] | removeEmpty + }) }} + {% endset %} + + {{ card({ + heading: "Screening history", + headingLevel: "2", + descriptionHtml: screeningHistoryHtml + }) }} + {% set medicalInformationHtml %} {% set riskFactorsList %}