diff --git a/app/controllers/book-into-a-clinic.js b/app/controllers/book-into-a-clinic.js index b8673bdd9..076dcf8d9 100644 --- a/app/controllers/book-into-a-clinic.js +++ b/app/controllers/book-into-a-clinic.js @@ -279,7 +279,7 @@ export const bookIntoClinicController = { }) response.locals.clinicLocationItems = clinicLocationItems } else if (view === 'clinic-date') { - const scheduledClinics = _.sortBy( + const scheduledClinicSessions = _.sortBy( Session.findAll(data).filter( (session) => session.type === SessionType.Clinic && @@ -290,7 +290,7 @@ export const bookIntoClinicController = { ) const clinicDateItems = [] - scheduledClinics.forEach((session) => { + scheduledClinicSessions.forEach((session) => { const midday = new Date(session.date) setMidday(midday) @@ -313,6 +313,10 @@ export const bookIntoClinicController = { }) }) response.locals.clinicDateItems = clinicDateItems + response.locals.clinicSummary = { + location: scheduledClinicSessions.at(0)?.formatted.location, + date: 'To be decided' + } } else if (view === 'appointment-time-range') { const session = Session.findOne(appointment.session_id, data) const availableTimesByHour = _.groupBy( @@ -338,6 +342,10 @@ export const bookIntoClinicController = { } }) response.locals.timeRangeItems = timeRangeItems + response.locals.clinicSummary = { + location: session.formatted.location, + date: session.formatted.date + } } else if (view === 'appointment-time') { const session = Session.findOne(appointment.session_id, data) const availableTimesByHour = _.groupBy( @@ -374,6 +382,10 @@ export const bookIntoClinicController = { } ) response.locals.appointmentTimeItems = appointmentTimeItems + response.locals.clinicSummary = { + location: session.formatted.location, + date: session.formatted.date + } } // All health questions use the same view diff --git a/app/enums.js b/app/enums.js index 5789f3f8c..9dcdc7277 100644 --- a/app/enums.js +++ b/app/enums.js @@ -819,3 +819,13 @@ export const VaccineSideEffect = { TemperatureShiver: 'a high temperature, or feeling hot and shivery', Unwell: 'generally feeling unwell' } + +/** + * @readonly + * @enum {string} + */ +export const LocationSearchType = { + Postcode: 'Postcode', + Outcode: 'Outcode', + Place: 'Place' +} diff --git a/app/locales/en.js b/app/locales/en.js index a9c77db8b..d18e75d5c 100644 --- a/app/locales/en.js +++ b/app/locales/en.js @@ -381,7 +381,7 @@ export const en = { hint: 'Each vaccine is given separately' }, fluChoice: { - title: 'Which of the flu vaccines do you agree to %s having?', + title: 'Which flu vaccine do you agree to %s having?', nasal: { label: 'I agree to the nasal spray vaccine', hint: 'This is the recommended option and gives the best protection against flu' @@ -427,7 +427,7 @@ export const en = { } }, preferredLocationMatches: { - title: 'We found 3 places that match “Newcastle”', + title: 'We found 3 places that match “%s”', hits: { label: 'Choose one of the following:' }, @@ -438,16 +438,19 @@ export const en = { hint: 'Select the same location and date as an earlier child, or find a different clinic.' }, clinicLocation: { - title: 'Choose a clinic location for %s', - hint: 'The following clinics are ordered by distance from NE12 7ET' + title: 'Choose a clinic location for %s’s appointment', + hint: 'The following clinics are ordered by distance from %s' }, clinicDate: { - title: 'Choose a clinic date for %s', - location: - 'Location: Killingworth Library, White Swan Centre, Killingworth, NE12 6SS', + title: 'Choose a clinic date for %s’s appointment', date: { label: 'Clinic date' }, + clinicSummary: { + location: { + label: 'Location' + } + }, hint: { morning: 'Morning available', afternoon: 'Afternoon available', @@ -457,7 +460,12 @@ export const en = { timeRange: { title: 'Choose a time range for %s’s appointment', clinicSummary: { - title: 'Clinic' + location: { + label: 'Location' + }, + date: { + label: 'Date' + } }, ranges: { label: 'Available time ranges' diff --git a/app/utils/clinic-appointment.js b/app/utils/clinic-appointment.js index dbcda99f4..f94fce258 100644 --- a/app/utils/clinic-appointment.js +++ b/app/utils/clinic-appointment.js @@ -1,8 +1,9 @@ import _ from 'lodash' -import { ReplyDecision } from '../enums.js' +import { LocationSearchType, ReplyDecision } from '../enums.js' import { ClinicAppointment, ClinicBooking, Session } from '../models.js' +import { getLocationSearchType } from './geolocation.js' import { camelToKebabCase } from './string.js' /** @@ -75,14 +76,23 @@ export const getAllAppointmentPaths = ( } : {}), [`/${booking_uuid}/new/${appointment_uuid}/preferred-location`]: { - [`/${booking_uuid}/new/${appointment_uuid}/clinic-location`]: { - data: 'transaction.preferredLocation', - value: 'NE12 7ET' + [`/${booking_uuid}/new/${appointment_uuid}/clinic-location`]: () => { + const searchTerm = sessionData.transaction.preferredLocation + const searchType = getLocationSearchType(searchTerm) + switch (searchType) { + case LocationSearchType.Postcode: + case LocationSearchType.Outcode: + sessionData.transaction.preferredPostcode = searchTerm + return true + case LocationSearchType.Place: + default: + return false + } } }, [`/${booking_uuid}/new/${appointment_uuid}/preferred-location-matches`]: { [`/${booking_uuid}/new/${appointment_uuid}/preferred-location`]: { - data: 'transaction.preferredLocation', + data: 'transaction.preferredPostcode', value: 'retry' } }, diff --git a/app/utils/geolocation.js b/app/utils/geolocation.js new file mode 100644 index 000000000..ce74cb958 --- /dev/null +++ b/app/utils/geolocation.js @@ -0,0 +1,28 @@ +import { LocationSearchType } from '../enums.js' + +/** + * Get the type of location represented by the location search term + * + * @param {string} searchTerm - the location that the user entered + * @returns {LocationSearchType|undefined} the type of value entered by the user + */ +export const getLocationSearchType = (searchTerm) => { + if (!searchTerm) { + return undefined + } + const cleanInput = searchTerm.trim().toUpperCase() + + // Regex for a full UK postcode (e.g., SW1A 1AA or NE12 7ET) + const fullPostcodeRegex = /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/ + if (fullPostcodeRegex.test(cleanInput)) { + return LocationSearchType.Postcode + } + + // Regex for a postcode Outcode (e.g., NE12, SW1A, B1) + const outcodeRegex = /^[A-Z]{1,2}\d[A-Z\d]?$/ + if (outcodeRegex.test(cleanInput)) { + return LocationSearchType.Outcode + } + + return LocationSearchType.Place +} diff --git a/app/views/book-into-a-clinic/form/_session-summary.njk b/app/views/book-into-a-clinic/form/_session-summary.njk new file mode 100644 index 000000000..b75671dd4 --- /dev/null +++ b/app/views/book-into-a-clinic/form/_session-summary.njk @@ -0,0 +1,15 @@ +{{ + summaryList({ + lastRowBorder: false, + rows: [ + { + key: { text: __('session.location.label') }, + value: { text: clinicSummary.location } + }, + { + key: { text: __('session.date.label') }, + value: { text: clinicSummary.date } + } + ] + }) +}} diff --git a/app/views/book-into-a-clinic/form/appointment-time-range.njk b/app/views/book-into-a-clinic/form/appointment-time-range.njk index 9943585ed..cf00fdd7a 100644 --- a/app/views/book-into-a-clinic/form/appointment-time-range.njk +++ b/app/views/book-into-a-clinic/form/appointment-time-range.njk @@ -8,24 +8,13 @@ caption: __("clinicBooking.appointment.caption", fullName) if childCount > 1 }) }} - {# - {{ summaryList({ - card: { - heading: __("clinicBooking.timeRange.clinicSummary.title"), - headingSize: "m" - }, - lastRowBorder: false, - rows: summaryRows(appointment, { - location: {}, - date: {} - }) - }) }} - #} + {% include "book-into-a-clinic/form/_session-summary.njk" %} {{ radios({ fieldset: { legend: { - text: __("clinicBooking.timeRange.ranges.label") + text: __("clinicBooking.timeRange.ranges.label"), + size: "m" } }, items: timeRangeItems, diff --git a/app/views/book-into-a-clinic/form/appointment-time.njk b/app/views/book-into-a-clinic/form/appointment-time.njk index 4476bf6fb..4d6f09f53 100644 --- a/app/views/book-into-a-clinic/form/appointment-time.njk +++ b/app/views/book-into-a-clinic/form/appointment-time.njk @@ -11,24 +11,13 @@ caption: __("clinicBooking.appointment.caption", fullName) if childCount > 1 }) }} - {# - {{ summaryList({ - card: { - heading: __("clinicBooking.time.clinicSummary.title"), - headingSize: "m" - }, - lastRowBorder: false, - rows: summaryRows(appointment, { - location: {}, - date: {} - }) - }) }} - #} + {% include "book-into-a-clinic/form/_session-summary.njk" %} {{ radios({ fieldset: { legend: { - text: __("clinicBooking.time.times.label") + text: __("clinicBooking.time.times.label"), + size: "m" } }, items: appointmentTimeItems, diff --git a/app/views/book-into-a-clinic/form/clinic-date.njk b/app/views/book-into-a-clinic/form/clinic-date.njk index 477e6350c..d19e814b8 100644 --- a/app/views/book-into-a-clinic/form/clinic-date.njk +++ b/app/views/book-into-a-clinic/form/clinic-date.njk @@ -8,12 +8,13 @@ caption: __("clinicBooking.appointment.caption", fullName) if childCount > 1 }) }} - {{ __("clinicBooking.clinicDate.location") | nhsukMarkdown }} + {% include "book-into-a-clinic/form/_session-summary.njk" %} {{ radios({ fieldset: { legend: { - text: __("clinicBooking.clinicDate.date.label") + text: __("clinicBooking.clinicDate.date.label"), + size: "m" } }, items: clinicDateItems, diff --git a/app/views/book-into-a-clinic/form/clinic-location.njk b/app/views/book-into-a-clinic/form/clinic-location.njk index 9430c6d7d..26bdf076b 100644 --- a/app/views/book-into-a-clinic/form/clinic-location.njk +++ b/app/views/book-into-a-clinic/form/clinic-location.njk @@ -1,6 +1,7 @@ {% extends "_layouts/form.njk" %} {% set title = __("clinicBooking.clinicLocation.title", firstName) %} +{% set postcode = data.transaction.preferredPostcode if data.transaction.preferredPostcode.length else "CV32" %} {% block form %} {{ radios({ @@ -12,7 +13,7 @@ }) } }, - hint: { text: __('clinicBooking.clinicLocation.hint') }, + hint: { text: __('clinicBooking.clinicLocation.hint', postcode) }, items: clinicLocationItems, decorate: "transaction.clinic_id" }) }} diff --git a/app/views/book-into-a-clinic/form/preferred-location-matches.njk b/app/views/book-into-a-clinic/form/preferred-location-matches.njk index 94ebd88a5..3bcb6619b 100644 --- a/app/views/book-into-a-clinic/form/preferred-location-matches.njk +++ b/app/views/book-into-a-clinic/form/preferred-location-matches.njk @@ -1,6 +1,6 @@ {% extends "_layouts/form.njk" %} -{% set title = __("clinicBooking.preferredLocationMatches.title") %} +{% set title = __("clinicBooking.preferredLocationMatches.title", transaction.preferredLocation) %} {% block form %} {{ appHeading({ @@ -13,15 +13,15 @@ legend: { text: __("clinicBooking.preferredLocationMatches.hits.label") } }, items: [{ - text: 'Newcastle upon Tyne, NE1', + text: transaction.preferredLocation + ' upon Tyne, NE1', value: 'NE1' }, { - text: 'Newcastle-under-Lyme, ST5', + text: transaction.preferredLocation + '-under-Lyme, ST5', value: 'ST5' }, { - text: 'Newcastle, SY7', + text: transaction.preferredLocation + ', SY7', value: 'SY7' }, { @@ -31,6 +31,6 @@ text: __('clinicBooking.preferredLocationMatches.tryAgain'), value: 'retry' }], - decorate: "transaction.preferredLocation" + decorate: "transaction.preferredPostcode" }) }} {% endblock %}