Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/assets/javascript/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,15 @@ class AppModal {
// forgot 'parentLayout or'. Bail out to a direct navigation rather than
// injecting the full site into the modal.
if (this.isFullPage(html)) {
// Capture the destination before close() — close() resets _loadUrl to
// null, so reading it after would navigate to a literal "null" URL.
const dest = this._loadUrl
console.warn(
'Modal: full page returned — falling back to direct navigation',
this._loadUrl
dest
)
this.close()
window.location.href = this._loadUrl
window.location.href = dest
return
}

Expand Down
1 change: 1 addition & 0 deletions app/assets/sass/components/_annotate-v2.scss
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ $ann-marker-shadow:
// Cursor preview: yellow circle+plus that follows the mouse on image panels
.app-ann-cursor-preview {
position: absolute;
z-index: 4; // above zoom button (z-index: 3) and active markers (z-index: 2)
width: $ann-marker-size;
height: $ann-marker-size;
border: $ann-marker-border-width solid $nhsuk-focus-colour;
Expand Down
8 changes: 4 additions & 4 deletions app/lib/generators/medical-information/symptoms-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ const generateSymptom = (options = {}) => {
const dateTypeWeights = {
...Object.fromEntries(DATE_RANGE_OPTIONS.map((range) => [range, 0.1])),
dateKnown: 0.3,
notSure: 0.1
notKnown: 0.1
}

// Generate basic symptom data matching form structure
Expand Down Expand Up @@ -332,16 +332,16 @@ const generateSymptom = (options = {}) => {
}
}

// 20% chance of additional info
// 20% chance of symptom notes
if (Math.random() < 0.2) {
const additionalInfoOptions = [
const symptomNotesOptions = [
'Noticed during self-examination',
'Partner noticed the change',
'Gets worse during certain times of month',
'No family history of breast problems',
'Concerned as mother had similar symptoms'
]
symptom.additionalInfo = faker.helpers.arrayElement(additionalInfoOptions)
symptom.symptomNotes = faker.helpers.arrayElement(symptomNotesOptions)
}

return symptom
Expand Down
16 changes: 8 additions & 8 deletions app/lib/generators/participant-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,20 @@ const generatePhoneNumbers = () => {
})

const result = {
mobilePhone: null,
homePhone: null
phone1: null,
phone2: null
}

switch (phoneConfig) {
case 'mobile_only':
result.mobilePhone = generateUKMobileNumber()
result.phone1 = generateUKMobileNumber()
break
case 'both':
result.mobilePhone = generateUKMobileNumber()
result.homePhone = generateUKHomeNumber()
result.phone1 = generateUKMobileNumber()
result.phone2 = generateUKHomeNumber()
break
case 'home_only':
result.homePhone = generateUKHomeNumber()
result.phone2 = generateUKHomeNumber()
break
}

Expand Down Expand Up @@ -216,8 +216,8 @@ const generateParticipant = ({
lastName: faker.person.lastName(),
dateOfBirth: generateDateOfBirth(participantRiskLevel),
address: generateBSUAppropriateAddress(assignedBSU),
mobilePhone: phoneNumbers.mobilePhone,
homePhone: phoneNumbers.homePhone,
phone1: phoneNumbers.phone1,
phone2: phoneNumbers.phone2,
email: `${faker.internet.username().toLowerCase()}@example.com`,
ethnicGroup: ethnicityData.ethnicGroup,
ethnicBackground: ethnicityData.ethnicBackground
Expand Down
18 changes: 8 additions & 10 deletions app/lib/utils/prior-mammograms.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,19 @@ const summarisePriorMammogram = (mammogram, options = {}) => {
let location = ''
switch (mammogram.location) {
case 'bsu':
location = mammogram.bsu || 'NHS breast screening unit'
location = 'At another BSU'
break
case 'otherUk':
location = mammogram.otherUk || 'Other UK location'
location = 'Elsewhere in the UK'
break
case 'otherNonUk':
location = mammogram.otherNonUk
? `Outside UK: ${mammogram.otherNonUk}`
: 'Outside UK'
location = 'Outside the UK'
break
case 'currentBsu':
location = unitName || 'Current BSU'
location = `At ${unitName || 'this BSU'}`
break
case 'preferNotToSay':
location = 'Location not given'
location = 'Location not provided'
break
default:
location = ''
Expand All @@ -152,14 +150,14 @@ const summarisePriorMammogram = (mammogram, options = {}) => {
// Date detail — combine formatted date and relative time into parenthesised suffix
const dateParts = []
if (mammogram.dateType === 'dateKnown' && mammogram.dateTaken) {
dateParts.push(formatDate(mammogram.dateTaken, 'MMMM YYYY'))
dateParts.push(formatDate(mammogram.dateTaken, 'MMM YYYY'))
if (mammogram._rawDate) {
dateParts.push(formatRelativeDate(mammogram._rawDate))
}
} else if (mammogram.dateType === 'moreThanSixMonths') {
dateParts.push(mammogram.approximateDate || 'over 6 months ago')
dateParts.push('over 6 months ago')
} else if (mammogram.dateType === 'lessThanSixMonths') {
dateParts.push(mammogram.approximateDate || 'less than 6 months ago')
dateParts.push('less than 6 months ago')
}

const dateDetail = dateParts.length > 0 ? `(${dateParts.join(', ')})` : ''
Expand Down
24 changes: 23 additions & 1 deletion app/lib/utils/reading.js
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,16 @@ const shouldShowComparePage = function (
* @param {object} [options] - Options for determining eligibility
* @returns {boolean} Whether the current user can read this event
*/
/**
* Check if an event has been deferred from reading
*
* @param {object} event - The event to check
* @returns {boolean} Whether the event has been deferred
*/
const isDeferred = (event) => {
return !!event?.imageReading?.deferral?.deferredAt
}

const canUserReadEvent = function (event, userId = null, options = {}) {
const { maxReadsPerEvent = 2 } = options

Expand All @@ -1383,6 +1393,11 @@ const canUserReadEvent = function (event, userId = null, options = {}) {
return false
}

// Can't read if event has been deferred
if (isDeferred(event)) {
return false
}

const metadata = getReadingMetadata(event)

// If we already have enough unique readers, no more reads needed
Expand Down Expand Up @@ -1821,17 +1836,23 @@ const getSessionReadingProgress = (

const resolvedTargetSize = session.targetSize || sessionEvents.length

// Count deferred events so they count toward the session target
const deferredCount = sessionEvents.filter(isDeferred).length

return {
...progress,
// How many events are currently loaded vs the overall target
populatedCount: sessionEvents.length,
targetSize: resolvedTargetSize,
// Deferred events count as 'done' for session progress purposes
deferredCount,
// Remaining reads against the target (not just currently loaded events)
targetRemaining: Math.max(
0,
resolvedTargetSize -
progress.userReadCount -
progress.userAwaitingPriorsCount
progress.userAwaitingPriorsCount -
deferredCount
)
}
}
Expand Down Expand Up @@ -1884,6 +1905,7 @@ module.exports = {
// Booleans
userHasReadEvent,
canUserReadEvent,
isDeferred,
hasReads,
needsArbitration,
needsFirstRead,
Expand Down
1 change: 1 addition & 0 deletions app/lib/utils/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ const getStatusTagColour = (status) => {
'prior_pending': 'orange',
'prior_requested': 'yellow',
'priors_requested': 'yellow',
'deferred': 'orange',
'prior_received': 'green',
'prior_not_available': 'grey',
'prior_not_needed': 'grey'
Expand Down
2 changes: 2 additions & 0 deletions app/lib/utils/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ const formatPhoneNumber = (phoneNumber) => {
if (!phoneNumber) return ''
if (typeof phoneNumber !== 'string') return phoneNumber

phoneNumber = phoneNumber.replace(/\s/g, '')

if (phoneNumber.startsWith('07')) {
return `${phoneNumber.slice(0, 5)} ${phoneNumber.slice(5)}`
}
Expand Down
53 changes: 37 additions & 16 deletions app/routes/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,17 @@ module.exports = (router) => {
return res.redirect(modalBreakout(`/clinics/${clinicId}/`))
}

// Normalise approximateDate in session now, before any redirect, so the
// warning page doesn't display a raw array (e.g. ",June 2025") caused by
// both conditional inputs being submitted together.
if (
previousMammogramTemp &&
Array.isArray(previousMammogramTemp.approximateDate)
) {
previousMammogramTemp.approximateDate =
previousMammogramTemp.approximateDate.find((v) => v) || ''
}

// Check if this is a recent mammogram (within 6 months)
const isRecentMammogram = checkIfRecentMammogram(previousMammogramTemp)

Expand Down Expand Up @@ -1131,17 +1142,12 @@ module.exports = (router) => {
})
}

// Validate whether the symptom has been investigated (required)
if (!data.event?.symptomTemp?.hasBeenInvestigated) {
validationErrors.push({
name: 'event[symptomTemp][hasBeenInvestigated]',
text: 'Select whether this has been investigated',
href: '#hasBeenInvestigated'
})
} else if (
data.event.symptomTemp.hasBeenInvestigated === 'yes' &&
!data.event.symptomTemp.investigatedDescription
) {
// Validate investigation details if the checkbox is checked
const hasBeenInvestigated = data.event?.symptomTemp?.hasBeenInvestigated
const isInvestigated = Array.isArray(hasBeenInvestigated)
? hasBeenInvestigated.includes('yes')
: hasBeenInvestigated === 'yes'
if (isInvestigated && !data.event?.symptomTemp?.investigatedDescription) {
validationErrors.push({
name: 'event[symptomTemp][investigatedDescription]',
text: 'Provide details of the investigation',
Expand Down Expand Up @@ -1210,8 +1216,7 @@ module.exports = (router) => {
id: symptomTemp.id || generateId(),
type: symptomType,
dateType: symptomTemp.dateType,
hasBeenInvestigated: symptomTemp.hasBeenInvestigated,
additionalInfo: symptomTemp.additionalInfo
symptomNotes: symptomTemp.symptomNotes
}

// For new symptoms, add the creation timestamp
Expand All @@ -1229,8 +1234,13 @@ module.exports = (router) => {
}
}

// Add investigation details if investigated
if (symptomTemp.hasBeenInvestigated === 'yes') {
// Normalise checkbox value and add investigation details if checked
const savedHasBeenInvestigated = symptomTemp.hasBeenInvestigated
const savedIsInvestigated = Array.isArray(savedHasBeenInvestigated)
? savedHasBeenInvestigated.includes('yes')
: savedHasBeenInvestigated === 'yes'
symptom.hasBeenInvestigated = savedIsInvestigated ? 'yes' : 'no'
if (savedIsInvestigated) {
symptom.investigatedDescription = symptomTemp.investigatedDescription
}

Expand All @@ -1247,7 +1257,7 @@ module.exports = (router) => {
].includes(symptomTemp.dateType)
) {
symptom.approximateDuration = symptomTemp.dateType
} else if (symptomTemp.dateType === 'notSure') {
} else if (symptomTemp.dateType === 'notKnown') {
delete symptom.approximateDuration
}

Expand Down Expand Up @@ -3342,6 +3352,17 @@ module.exports = (router) => {
res.redirect(modalBreakout(returnUrl))
}
)
// Save participant data when contact details are updated from the participant tab.
// The contact-details form posts back to the participant tab URL via referrerChain,
// so we need this POST handler to persist the temp participant to the participants array.
router.post('/clinics/:clinicId/events/:eventId/participant', (req, res) => {
const { clinicId, eventId } = req.params
const data = req.session.data
saveTempParticipantToParticipant(data)
req.flash('success', 'Participant details updated')
res.redirect(`/clinics/${clinicId}/events/${eventId}/participant`)
})

// General purpose dynamic template route for events
// This should come after any more specific routes
router.get(
Expand Down
Loading