From 506659f438e242ce750bf5eabb66173826b33f2b Mon Sep 17 00:00:00 2001 From: roobottom Date: Tue, 5 May 2026 15:41:09 +0100 Subject: [PATCH 01/11] update main navigation Co-authored-by: Copilot --- app/data/session-data-defaults.js | 9 ++---- app/views/components/action-control-bar.njk | 29 ++++++++++++++++++ app/views/site/availability/day.html | 23 ++++---------- app/views/site/availability/month.html | 14 ++++----- app/views/site/availability/week.html | 14 ++++----- app/views/site/clinics/clinics.html | 34 +++++++++++++++++---- 6 files changed, 77 insertions(+), 46 deletions(-) create mode 100644 app/views/components/action-control-bar.njk diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index 383863e..4534138 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -120,15 +120,10 @@ const base = { hideCard: true }, { - text: 'View appointments', - description: 'View and manage appointments for your site', + text: 'Clinics', + description: 'View appointments for your site', hrefTemplate: '/site/:id/availability/day' }, - { - text: 'Manage clinics', - description: 'Create clinics and review recently created clinic series', - hrefTemplate: '/site/:id/clinics' - }, { text: 'Change site details', description: 'Change site details and accessibility information', diff --git a/app/views/components/action-control-bar.njk b/app/views/components/action-control-bar.njk new file mode 100644 index 0000000..107aae4 --- /dev/null +++ b/app/views/components/action-control-bar.njk @@ -0,0 +1,29 @@ +{% from 'nhsuk/components/button/macro.njk' import button %} + +{% macro appActionControlBar(params) %} + {% set siteId = params.siteId %} + {% set createClinicHref = params.createClinicHref or ('/site/' ~ siteId ~ '/clinics/type-of-clinc?new=1') %} + {% set cancelDateRangeHref = params.cancelDateRangeHref or ('/site/' ~ siteId ~ '/cancel-availability/dates') %} + +
+ {{ button({ + text: 'Create clinics', + classes: 'nhsuk-button--small', + href: createClinicHref + }) }} + + {{ button({ + text: 'Cancel a date range', + classes: 'nhsuk-button--secondary nhsuk-button--small', + href: cancelDateRangeHref + }) }} + + {% if params.print %} + {{ button({ + text: 'Print page', + classes: 'nhsuk-button--secondary nhsuk-button--small', + href: params.print + }) }} + {% endif %} +
+{% endmacro %} \ No newline at end of file diff --git a/app/views/site/availability/day.html b/app/views/site/availability/day.html index ed7a084..ce7c5c7 100644 --- a/app/views/site/availability/day.html +++ b/app/views/site/availability/day.html @@ -1,5 +1,6 @@ {% extends '../../layouts/layout.html' %} {% from '../../components/secondary-navigation.njk' import appSecondaryNavigation %} +{% from '../../components/action-control-bar.njk' import appActionControlBar %} {% set pageName = date | formatDate('EEEE, d LLLL yyyy') %} @@ -24,18 +25,15 @@ href: '/site/' ~ site_id ~ '/availability/month' }, { - text: 'Clinics', - href: '/site/' ~ site_id ~ '/clinics', - primary: true - } if features.allAvailability + text: 'All clinics', + href: '/site/' ~ site_id ~ '/clinics' + } ] }) }} - {% if features.cancelDateRange %} - {% include "../../includes/addCancelButtons.njk" %} - {% endif %} -

{{ pageName }}

+ {{ appActionControlBar({ siteId: site_id, print: '#' }) }} +

{{ pageName }}

{{ pagination({ previous: { @@ -47,14 +45,6 @@

{{ pageName }}

href: "/site/"~site_id~"/availability/day?date="~tomorrow } }) }} - - - {% macro orphanNote(count) -%} - {% set isAre = 'is' if count == 1 else 'are' %} - {% set appointmentAppointments = 'appointment' if count == 1 else 'appointments' %} - {% set itThem = 'it' if count == 1 else 'them' %} - {{ count }} booked {{appointmentAppointments}} {{isAre}} still scheduled until you cancel {{itThem}}. - {%- endmacro %} {% macro appointmentForToday(opts) %} {# calculate if there's any booked appointments of this type #} @@ -68,7 +58,6 @@

{{ pageName }}

{{ opts.title }}

{% if hasBookedAppointments %} - Print {{ opts.title | lower }} diff --git a/app/views/site/availability/month.html b/app/views/site/availability/month.html index 6030c7b..fc93e7a 100644 --- a/app/views/site/availability/month.html +++ b/app/views/site/availability/month.html @@ -1,5 +1,6 @@ {% extends '../../layouts/layout.html' %} {% from '../../components/secondary-navigation.njk' import appSecondaryNavigation %} +{% from '../../components/action-control-bar.njk' import appActionControlBar %} {% from 'nhsuk/components/card/macro.njk' import card %} {% set pageName = currentDate | formatDate("LLLL yyyy") %} @@ -24,19 +25,16 @@ current: true }, { - text: 'Clinics', - href: '/site/' ~ site_id ~ '/clinics', - primary: true - } if features.allAvailability + text: 'All clinics', + href: '/site/' ~ site_id ~ '/clinics' + } ] }) }} - {% if features.cancelDateRange %} - {% include "../../includes/addCancelButtons.njk" %} - {% endif %} + {{ appActionControlBar({ siteId: site_id }) }} -

{{ pageName }}

+

{{ pageName }}

{{ pagination({ previous: { diff --git a/app/views/site/availability/week.html b/app/views/site/availability/week.html index 71e6460..14ff37c 100644 --- a/app/views/site/availability/week.html +++ b/app/views/site/availability/week.html @@ -1,5 +1,6 @@ {% extends '../../layouts/layout.html' %} {% from '../../components/secondary-navigation.njk' import appSecondaryNavigation %} +{% from '../../components/action-control-bar.njk' import appActionControlBar %} {% from 'nhsuk/components/card/macro.njk' import card %} {% set pageName = week[0] | formatDate('d LLLL') ~ ' to ' ~ week[6] | formatDate('d LLLL yyyy') %} @@ -24,19 +25,16 @@ href: '/site/' ~ site_id ~ '/availability/month' }, { - text: 'Clinics', - href: '/site/' ~ site_id ~ '/clinics', - primary: true - } if features.allAvailability + text: 'All clinics', + href: '/site/' ~ site_id ~ '/clinics' + } ] }) }} - {% if features.cancelDateRange %} - {% include "../../includes/addCancelButtons.njk" %} - {% endif %} + {{ appActionControlBar({ siteId: site_id }) }} -

{{ pageName }}

+

{{ pageName }}

{{ pagination({ previous: { diff --git a/app/views/site/clinics/clinics.html b/app/views/site/clinics/clinics.html index 088f4e8..b3b45a9 100644 --- a/app/views/site/clinics/clinics.html +++ b/app/views/site/clinics/clinics.html @@ -1,4 +1,6 @@ {% extends '../../layouts/layout.html' %} +{% from '../../components/secondary-navigation.njk' import appSecondaryNavigation %} +{% from '../../components/action-control-bar.njk' import appActionControlBar %} {% set pageName = "Clinics" %} @@ -69,13 +71,33 @@ {% block content %}
-

{{ pageName }}

+ {{ + appSecondaryNavigation({ + items: [ + { + text: 'Day view', + href: '/site/' ~ site_id ~ '/availability/day' + }, + { + text: 'Week view', + href: '/site/' ~ site_id ~ '/availability/week' + }, + { + text: 'Month view', + href: '/site/' ~ site_id ~ '/availability/month' + }, + { + text: 'All clinics', + href: '/site/' ~ site_id ~ '/clinics', + current: true + } + ] + }) + }} + + {{ appActionControlBar({ siteId: site_id }) }} - {{ button({ - text: "Create clinic", - classes: "nhsuk-button--secondary nhsuk-button--small", - href: "/site/" + site_id + "/clinics/type-of-clinc?new=1" - }) }} +

{{ pageName }}

Active clinics

From d50651fdace69bb8a43284bc5e862cd46fb872fe Mon Sep 17 00:00:00 2001 From: roobottom Date: Tue, 5 May 2026 15:43:35 +0100 Subject: [PATCH 02/11] add counts to day tabs Co-authored-by: Copilot --- app/views/site/availability/day.html | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/views/site/availability/day.html b/app/views/site/availability/day.html index ce7c5c7..8279910 100644 --- a/app/views/site/availability/day.html +++ b/app/views/site/availability/day.html @@ -114,10 +114,21 @@

{{ opts.title }}

{% endmacro %} + {% set daySlots = slots[date] or [] %} + {% set scheduledCount = 0 %} + {% set cancelledCount = 0 %} + {% for slot in daySlots %} + {% if slot.booking_status == 'scheduled' or slot.booking_status == 'orphaned' %} + {% set scheduledCount = scheduledCount + 1 %} + {% elif slot.booking_status == 'cancelled' %} + {% set cancelledCount = cancelledCount + 1 %} + {% endif %} + {% endfor %} + {{ tabs({ items: [ { - label: "Scheduled", + label: "Scheduled (" ~ scheduledCount ~ ")", id: "scheduled", panel: { html: appointmentForToday({ @@ -128,7 +139,7 @@

{{ opts.title }}

} }, { - label: "Cancelled", + label: "Cancelled (" ~ cancelledCount ~ ")", id: "cancelled", panel: { html: appointmentForToday({ From 9c6cd52be1747871b4298e636d59765163fc45b6 Mon Sep 17 00:00:00 2001 From: roobottom Date: Tue, 5 May 2026 16:01:23 +0100 Subject: [PATCH 03/11] Refine availability views with relative labels and tab counts --- app/routes/base.js | 58 +++++++++++++++++++++++--- app/views/site/availability/day.html | 6 +-- app/views/site/availability/month.html | 6 +-- app/views/site/availability/week.html | 6 +-- 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/app/routes/base.js b/app/routes/base.js index 15911d5..e461d0e 100644 --- a/app/routes/base.js +++ b/app/routes/base.js @@ -15,6 +15,42 @@ function getToday() { return override_today || DateTime.now().toFormat('yyyy-MM-dd'); } +function getRelativeDayLabel(dateISO, todayISO) { + const target = DateTime.fromISO(dateISO || '').startOf('day'); + const today = DateTime.fromISO(todayISO || '').startOf('day'); + if (!target.isValid || !today.isValid) return null; + + const diffDays = Math.round(target.diff(today, 'days').days); + if (diffDays === 0) return 'Today'; + if (diffDays === -1) return 'Yesterday'; + if (diffDays === 1) return 'Tomorrow'; + return null; +} + +function getRelativeWeekLabel(weekStartISO, todayISO) { + const targetWeek = DateTime.fromISO(weekStartISO || '').startOf('week'); + const thisWeek = DateTime.fromISO(todayISO || '').startOf('week'); + if (!targetWeek.isValid || !thisWeek.isValid) return null; + + const diffWeeks = Math.round(targetWeek.diff(thisWeek, 'weeks').weeks); + if (diffWeeks === 0) return 'This week'; + if (diffWeeks === -1) return 'Last week'; + if (diffWeeks === 1) return 'Next week'; + return null; +} + +function getRelativeMonthLabel(monthDateISO, todayISO) { + const targetMonth = DateTime.fromISO(monthDateISO || '').startOf('month'); + const thisMonth = DateTime.fromISO(todayISO || '').startOf('month'); + if (!targetMonth.isValid || !thisMonth.isValid) return null; + + const diffMonths = Math.round(targetMonth.diff(thisMonth, 'months').months); + if (diffMonths === 0) return 'This month'; + if (diffMonths === -1) return 'Last month'; + if (diffMonths === 1) return 'Next month'; + return null; +} + function asArray(value) { if (Array.isArray(value)) return value; if (value === undefined || value === null || value === '') return []; @@ -2274,12 +2310,18 @@ router.get('/site/:id/debug/recurring-expansion', (req, res) => { // ----------------------------------------------------------------------------- router.get('/site/:id/availability/day', (req, res) => { const date = req.query.date || getToday(); + const today = getToday(); + const tomorrow = DateTime.fromISO(date).plus({ days: 1 }).toISODate(); + const yesterday = DateTime.fromISO(date).minus({ days: 1 }).toISODate(); res.render('site/availability/day', { date, - today: getToday(), - tomorrow: DateTime.fromISO(date).plus({ days: 1 }).toISODate(), - yesterday: DateTime.fromISO(date).minus({ days: 1 }).toISODate() + today, + tomorrow, + yesterday, + dayHeading: getRelativeDayLabel(date, today), + previousDayLabel: getRelativeDayLabel(yesterday, today), + nextDayLabel: getRelativeDayLabel(tomorrow, today) }); }); @@ -2324,7 +2366,10 @@ router.get('/site/:id/availability/week', (req, res) => { week, weekDays, previousWeek, - nextWeek + nextWeek, + weekHeading: getRelativeWeekLabel(week[0], today), + previousWeekLabel: getRelativeWeekLabel(previousWeek.start, today), + nextWeekLabel: getRelativeWeekLabel(nextWeek.start, today) }); }); @@ -2349,7 +2394,10 @@ router.get('/site/:id/availability/month', (req, res) => { currentDate: monthData.currentDate, previousMonthDate: monthData.previousMonthDate, nextMonthDate: monthData.nextMonthDate, - monthWeeks + monthWeeks, + monthHeading: getRelativeMonthLabel(monthData.currentDate, today), + previousMonthLabel: getRelativeMonthLabel(monthData.previousMonthDate, today), + nextMonthLabel: getRelativeMonthLabel(monthData.nextMonthDate, today) }); }); diff --git a/app/views/site/availability/day.html b/app/views/site/availability/day.html index 8279910..3f0a40f 100644 --- a/app/views/site/availability/day.html +++ b/app/views/site/availability/day.html @@ -2,7 +2,7 @@ {% from '../../components/secondary-navigation.njk' import appSecondaryNavigation %} {% from '../../components/action-control-bar.njk' import appActionControlBar %} -{% set pageName = date | formatDate('EEEE, d LLLL yyyy') %} +{% set pageName = dayHeading or (date | formatDate('EEEE, d LLLL yyyy')) %} {% block content %} @@ -37,11 +37,11 @@

{{ pageName }}

{{ pagination({ previous: { - labelText: yesterday | formatDate('EEE, d LLL'), + labelText: previousDayLabel or (yesterday | formatDate('EEE, d LLL')), href: "/site/"~site_id~"/availability/day?date="~yesterday }, next: { - labelText: tomorrow | formatDate('EEE, d LLL'), + labelText: nextDayLabel or (tomorrow | formatDate('EEE, d LLL')), href: "/site/"~site_id~"/availability/day?date="~tomorrow } }) }} diff --git a/app/views/site/availability/month.html b/app/views/site/availability/month.html index fc93e7a..8e09756 100644 --- a/app/views/site/availability/month.html +++ b/app/views/site/availability/month.html @@ -3,7 +3,7 @@ {% from '../../components/action-control-bar.njk' import appActionControlBar %} {% from 'nhsuk/components/card/macro.njk' import card %} -{% set pageName = currentDate | formatDate("LLLL yyyy") %} +{% set pageName = monthHeading or (currentDate | formatDate("LLLL yyyy")) %} {% block content %}
@@ -38,11 +38,11 @@

{{ pageName }}

{{ pagination({ previous: { - labelText: previousMonthDate | formatDate('LLLL yyyy'), + labelText: previousMonthLabel or (previousMonthDate | formatDate('LLLL yyyy')), href: "/site/" ~ site_id ~ "/availability/month?date=" ~ previousMonthDate }, next: { - labelText: nextMonthDate | formatDate('LLLL yyyy'), + labelText: nextMonthLabel or (nextMonthDate | formatDate('LLLL yyyy')), href: "/site/" ~ site_id ~ "/availability/month?date=" ~ nextMonthDate } }) }} diff --git a/app/views/site/availability/week.html b/app/views/site/availability/week.html index 14ff37c..63c5c66 100644 --- a/app/views/site/availability/week.html +++ b/app/views/site/availability/week.html @@ -3,7 +3,7 @@ {% from '../../components/action-control-bar.njk' import appActionControlBar %} {% from 'nhsuk/components/card/macro.njk' import card %} -{% set pageName = week[0] | formatDate('d LLLL') ~ ' to ' ~ week[6] | formatDate('d LLLL yyyy') %} +{% set pageName = weekHeading or (week[0] | formatDate('d LLLL') ~ ' to ' ~ week[6] | formatDate('d LLLL yyyy')) %} {% block content %}
@@ -38,11 +38,11 @@

{{ pageName }}

{{ pagination({ previous: { - labelText: previousWeek.start | formatDate('d') ~ " to " ~ previousWeek.end | formatDate('d LLL'), + labelText: previousWeekLabel or (previousWeek.start | formatDate('d') ~ " to " ~ previousWeek.end | formatDate('d LLL')), href: "/site/" ~ site_id ~ "/availability/week?date=" ~ previousWeek.start }, next: { - labelText: nextWeek.start | formatDate('d') ~ " to " ~ nextWeek.end | formatDate('d LLL'), + labelText: nextWeekLabel or (nextWeek.start | formatDate('d') ~ " to " ~ nextWeek.end | formatDate('d LLL')), href: "/site/" ~ site_id ~ "/availability/week?date=" ~ nextWeek.start } }) }} From b26f257d4de2b1b9f3142c3769b7ba8381c3ca3d Mon Sep 17 00:00:00 2001 From: roobottom Date: Tue, 5 May 2026 16:20:30 +0100 Subject: [PATCH 04/11] added clinics running today Co-authored-by: Copilot --- app/routes/base.js | 88 ++++++++++++++++++++++++++-- app/routes/change-session.js | 16 ++++- app/views/site/availability/day.html | 56 ++++++++++++++++++ 3 files changed, 155 insertions(+), 5 deletions(-) diff --git a/app/routes/base.js b/app/routes/base.js index e461d0e..4109b66 100644 --- a/app/routes/base.js +++ b/app/routes/base.js @@ -1247,7 +1247,7 @@ function slotMatchesSession(slot, session) { && (!slot?.recurringSessionId || !session?.recurringId || String(slot.recurringSessionId) === String(session.recurringId)); } -function buildWeekAvailabilitySummary(week, dailyAvailability, slotsByDate, servicesById, siteBookings, siteId, today, recurringSessionsById = {}) { +function buildWeekAvailabilitySummary(week, dailyAvailability, slotsByDate, servicesById, siteBookings, siteId, today, recurringSessionsById = {}, backHref = null) { return week.map((day) => { const sessions = sortSessionsForAvailability(dailyAvailability?.[day]?.sessions); const dateSlots = asArray(slotsByDate?.[day]); @@ -1258,6 +1258,10 @@ function buildWeekAvailabilitySummary(week, dailyAvailability, slotsByDate, serv const totalSlots = sessionSlots.length; const resolvedLabel = session.label || recurringSessionsById?.[session?.recurringId]?.label || ''; + const changeHrefBase = day < today || !session?.recurringId + ? null + : `/site/${siteId}/change/session/${session.id}`; + return { id: session.id, label: resolvedLabel, @@ -1274,7 +1278,9 @@ function buildWeekAvailabilitySummary(week, dailyAvailability, slotsByDate, serv })), bookedTotal, unbookedTotal: Math.max(0, totalSlots - bookedTotal), - actionHref: day < today || !session?.recurringId ? null : `/site/${siteId}/change/session/${session.id}` + actionHref: changeHrefBase + ? `${changeHrefBase}${backHref ? `?back=${encodeURIComponent(backHref)}` : ''}` + : null }; }); @@ -1335,7 +1341,8 @@ function buildMonthAvailabilitySummary(weekRanges, dailyAvailability, slotsByDat siteBookings, siteId, today, - recurringSessionsById + recurringSessionsById, + null ); const services = new Map(); @@ -2309,16 +2316,88 @@ router.get('/site/:id/debug/recurring-expansion', (req, res) => { // VIEW AVAILABILITY // ----------------------------------------------------------------------------- router.get('/site/:id/availability/day', (req, res) => { + const data = req.session.data; + const site_id = req.site_id; const date = req.query.date || getToday(); const today = getToday(); const tomorrow = DateTime.fromISO(date).plus({ days: 1 }).toISODate(); const yesterday = DateTime.fromISO(date).minus({ days: 1 }).toISODate(); + const daySummary = buildWeekAvailabilitySummary( + [date], + res.locals.dailyAvailability, + res.locals.slots, + data.services || {}, + data?.bookings?.[site_id] || {}, + site_id, + today, + data?.recurring_sessions?.[site_id] || {}, + `/site/${site_id}/availability/day?date=${date}` + )[0] || { + date, + sessions: [], + totalAppointments: 0, + bookedAppointments: 0, + unbookedAppointments: 0, + isToday: date === today, + isPast: date < today, + dayViewHref: `/site/${site_id}/availability/day?date=${date}` + }; + + if ((daySummary.sessions || []).length === 0 && (res.locals.dailyAvailability?.[date]?.sessions || []).length > 0) { + const dateSlots = asArray(res.locals.slots?.[date]); + const sessions = sortSessionsForAvailability(res.locals.dailyAvailability?.[date]?.sessions); + const siteBookings = data?.bookings?.[site_id] || {}; + const servicesById = data.services || {}; + + const fallbackSessions = sessions.map((session) => { + const sessionSlots = dateSlots.filter((slot) => ( + (slot?.sessionId && session?.id && String(slot.sessionId) === String(session.id)) + || ( + !slot?.sessionId + && slot?.group?.start === session?.from + && slot?.group?.end === session?.until + && (!slot?.recurringSessionId || !session?.recurringId || String(slot.recurringSessionId) === String(session.recurringId)) + ) + )); + + const bookedTotal = sessionSlots.filter((slot) => slot?.booking_status === 'scheduled').length; + const totalSlots = sessionSlots.length; + const resolvedLabel = session.label || data?.recurring_sessions?.[site_id]?.[session?.recurringId]?.label || ''; + + return { + id: session.id, + label: resolvedLabel, + from: session.from, + until: session.until, + services: asArray(session.services).map((serviceId) => ({ + id: serviceId, + name: servicesById?.[serviceId]?.name || serviceId, + bookedCount: sessionSlots.filter((slot) => ( + slot?.booking_status === 'scheduled' + && slot?.booking_id + && siteBookings?.[slot.booking_id]?.service === serviceId + )).length + })), + bookedTotal, + unbookedTotal: Math.max(0, totalSlots - bookedTotal), + actionHref: date < today || !session?.recurringId + ? null + : `/site/${site_id}/change/session/${session.id}?back=${encodeURIComponent(`/site/${site_id}/availability/day?date=${date}`)}` + }; + }); + + daySummary.sessions = fallbackSessions; + daySummary.totalAppointments = fallbackSessions.reduce((sum, session) => sum + session.bookedTotal + session.unbookedTotal, 0); + daySummary.bookedAppointments = fallbackSessions.reduce((sum, session) => sum + session.bookedTotal, 0); + daySummary.unbookedAppointments = Math.max(0, daySummary.totalAppointments - daySummary.bookedAppointments); + } res.render('site/availability/day', { date, today, tomorrow, yesterday, + daySummary, dayHeading: getRelativeDayLabel(date, today), previousDayLabel: getRelativeDayLabel(yesterday, today), nextDayLabel: getRelativeDayLabel(tomorrow, today) @@ -2357,7 +2436,8 @@ router.get('/site/:id/availability/week', (req, res) => { data?.bookings?.[site_id] || {}, site_id, today, - data?.recurring_sessions?.[site_id] || {} + data?.recurring_sessions?.[site_id] || {}, + `/site/${site_id}/availability/week?date=${startFromDate}` ); res.render('site/availability/week', { diff --git a/app/routes/change-session.js b/app/routes/change-session.js index 6a1c027..5e87732 100644 --- a/app/routes/change-session.js +++ b/app/routes/change-session.js @@ -107,6 +107,14 @@ function changeStepPath(siteId, itemId, step) { return `${changeSummaryPath(siteId, itemId)}/${step}`; } +function normalizeBackHref(back) { + if (typeof back !== 'string') return null; + const value = back.trim(); + if (!value.startsWith('/')) return null; + if (value.startsWith('//')) return null; + return value; +} + function changeFieldToStep(field) { switch (field) { case 'name': @@ -549,9 +557,14 @@ function ensureChangeStateForSession(req, res) { const data = req.session.data; const siteId = req.site_id; const itemId = req.params.itemId; + const requestedBackHref = normalizeBackHref(req.query?.back); const existing = getChangeState(data); if (existing && existing.siteId === siteId && existing.itemId === itemId) { + if (requestedBackHref) { + existing.returnTo = requestedBackHref; + setChangeState(data, existing); + } return existing; } @@ -570,6 +583,7 @@ function ensureChangeStateForSession(req, res) { originalParent: clone(parentModel), originalChild: clone(originalChild), draft: buildChildDraft(parentModel, target.session, target.date), + returnTo: requestedBackHref || null, currentEditStep: null, bookingAction: null, affectedBookingIds: [] @@ -601,7 +615,7 @@ router.get('/site/:id/change/:type/:itemId', (req, res) => { draft: state.draft, date: state.date, displayDate: formatDisplayDate(state.date), - weekHref: weekViewHref(req.site_id, state.date) + weekHref: state.returnTo || weekViewHref(req.site_id, state.date) }); }); diff --git a/app/views/site/availability/day.html b/app/views/site/availability/day.html index 3f0a40f..b8d0438 100644 --- a/app/views/site/availability/day.html +++ b/app/views/site/availability/day.html @@ -114,9 +114,58 @@

{{ opts.title }}

{% endmacro %} + {% macro clinicsRunningToday() %} + {% set sessions = daySummary.sessions or [] %} + + {% if sessions | length %} +
+ + + + + + + + + + + {% for session in sessions %} + + + + + + + + {% endfor %} + +
Name and timeServicesBookedUnbookedAction
+ {% if session.label %} + {{ session.label }}
+ {% endif %} + {{ session.from | nhsTime }} to {{ session.until | nhsTime }} +
+ {% for service in session.services %} + {{ service.name }}{% if not loop.last %}
{% endif %} + {% endfor %} +
+ {% for service in session.services %} + {{ service.bookedCount }} booked{% if not loop.last %}
{% endif %} + {% endfor %} +
{{ session.unbookedTotal }} unbooked + {% if session.actionHref %} + Change + {% endif %} +
+ {% else %} +

No clinics running today.

+ {% endif %} + {% endmacro %} + {% set daySlots = slots[date] or [] %} {% set scheduledCount = 0 %} {% set cancelledCount = 0 %} + {% set clinicsRunningCount = (daySummary.sessions or []) | length %} {% for slot in daySlots %} {% if slot.booking_status == 'scheduled' or slot.booking_status == 'orphaned' %} {% set scheduledCount = scheduledCount + 1 %} @@ -147,6 +196,13 @@

{{ opts.title }}

title: 'Cancelled appointments' }) } + }, + { + label: "Clinics running today (" ~ clinicsRunningCount ~ ")", + id: "clinics-running-today", + panel: { + html: clinicsRunningToday() + } } ] }) }} From e36dd3644e8956aed0bf945bf6a6fb4a01340171 Mon Sep 17 00:00:00 2001 From: roobottom Date: Tue, 5 May 2026 16:29:27 +0100 Subject: [PATCH 05/11] added view day link to week view Co-authored-by: Copilot --- app/views/site/availability/week.html | 4 ++++ app/views/site/cancel-availability/dates.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/site/availability/week.html b/app/views/site/availability/week.html index 63c5c66..76b0ab7 100644 --- a/app/views/site/availability/week.html +++ b/app/views/site/availability/week.html @@ -109,6 +109,10 @@

{{ pageName }}

+

+ View day +

+ {% else %}

No appointments.

{% endif %} diff --git a/app/views/site/cancel-availability/dates.html b/app/views/site/cancel-availability/dates.html index f2caa18..c4a549f 100644 --- a/app/views/site/cancel-availability/dates.html +++ b/app/views/site/cancel-availability/dates.html @@ -4,7 +4,7 @@ {% block beforeContent %} {{ backLink({ - href: '/site/' ~ site_id ~ '/cancel-availability' + href: '/site/' ~ site_id ~ '/availability/day' }) }} {% endblock %} From 41da5831e242f115a5eea5559db2f8e899618ca6 Mon Sep 17 00:00:00 2001 From: roobottom Date: Tue, 5 May 2026 17:34:46 +0100 Subject: [PATCH 06/11] experimenting with clinic cards Co-authored-by: Copilot --- app/assets/sass/components/clinic-card.scss | 119 +++++++++++++++++ app/assets/sass/main.scss | 1 + app/routes/base.js | 1 + app/views/site/clinics/clinics.html | 136 ++++++++++++++++---- 4 files changed, 235 insertions(+), 22 deletions(-) create mode 100644 app/assets/sass/components/clinic-card.scss diff --git a/app/assets/sass/components/clinic-card.scss b/app/assets/sass/components/clinic-card.scss new file mode 100644 index 0000000..5f65bae --- /dev/null +++ b/app/assets/sass/components/clinic-card.scss @@ -0,0 +1,119 @@ +@use "nhsuk-frontend/dist/nhsuk" as *; + +// Support both class names while templates are being refined. +.app-clinic, +.app-clinc { + border: 1px solid $nhsuk-border-colour; + background-color: nhsuk-colour("white"); + @include nhsuk-responsive-margin(4, "bottom"); + + &__header, + .app-clinic__header { + display: flex; + gap: nhsuk-spacing(3); + align-items: flex-start; + justify-content: space-between; + @include nhsuk-responsive-padding(4); + + @include nhsuk-media-query($until: tablet) { + flex-direction: column; + align-items: stretch; + } + } + + &__title, + .app-clinic__title { + .nhsuk-tag { + @include nhsuk-responsive-margin(2, "bottom"); + } + + .nhsuk-heading-xs { + margin-bottom: 0; + } + } + + &__nav, + .app-clinic__nav { + margin-left: auto; + + @include nhsuk-media-query($until: tablet) { + margin-left: 0; + } + } + + &__nav-list, + .app-clinic__nav-list { + margin: 0; + padding: 0; + list-style: none; + display: flex; + gap: nhsuk-spacing(3); + + @include nhsuk-media-query($until: tablet) { + flex-wrap: wrap; + } + } + + &__nav-item, + .app-clinic__nav-item { + margin: 0; + } + + &__content, + .app-clinic__content { + @include nhsuk-font-size(19); + @include nhsuk-responsive-padding(4); + padding-top: 0; + border-bottom: 1px solid $nhsuk-border-colour; + } + + &__footer, + .app-clinic__footer { + @include nhsuk-responsive-padding(4); + padding-top: 0; + padding-bottom: 0; + background-color: nhsuk-colour("grey-5"); + + .nhsuk-body-s { + margin-top: 0; + } + + .app-clinic__changes-table { + margin-bottom: 0; + + .nhsuk-table__header, + .nhsuk-table__cell { + @include nhsuk-font-size(16); + line-height: 1.35; + } + + .nhsuk-table__body .nhsuk-table__row:last-child .nhsuk-table__cell { + border-bottom: 0; + } + } + + .app-clinic__changes-definitions { + margin: 0; + } + + .app-clinic__changes-pair { + @include nhsuk-responsive-margin(2, "bottom"); + + &:last-child { + margin-bottom: 0; + } + } + + .app-clinic__changes-term { + @include nhsuk-font-size(16); + color: $nhsuk-secondary-text-colour; + margin: 0; + } + + .app-clinic__changes-description { + @include nhsuk-font-size(16); + line-height: 1.35; + margin: 0; + } + } +} diff --git a/app/assets/sass/main.scss b/app/assets/sass/main.scss index 984c0fe..04cf372 100755 --- a/app/assets/sass/main.scss +++ b/app/assets/sass/main.scss @@ -4,6 +4,7 @@ @forward "components/app-card"; @forward "components/secondary-navigation"; @forward "components/appointments-summary"; +@forward "components/clinic-card"; diff --git a/app/routes/base.js b/app/routes/base.js index 4109b66..85fa1c4 100644 --- a/app/routes/base.js +++ b/app/routes/base.js @@ -1179,6 +1179,7 @@ function buildSessionHistory(siteRecurringSessions, startDate = null, endDate = from: session.from, until: session.until, services: session.services || [], + childSessions: asArray(session.childSessions || []).slice().sort((a, b) => String(a?.date || '').localeCompare(String(b?.date || ''))), capacity: Number(session.capacity) || 0, slotLength: Number(session.slotLength) || 0 }); diff --git a/app/views/site/clinics/clinics.html b/app/views/site/clinics/clinics.html index b3b45a9..b99b37e 100644 --- a/app/views/site/clinics/clinics.html +++ b/app/views/site/clinics/clinics.html @@ -67,6 +67,117 @@ {% endif %} {% endmacro %} +{% macro clinicCard(displaySession, editSessionId) %} + {% macro childServiceChanges(childSession) %} + {% set operations = childSession.services or [] %} + {% if operations | length == 0 %} + Change services + {% elif operations[0] is string %} + Change services
+ Services replaced + {% else %} + {% for operation in operations %} + {% set serviceName = data.services[operation.service].name if data.services[operation.service] else operation.service %} + Change services
+ {% if operation.operation == 'add' %} + Added {{ serviceName }} + {% elif operation.operation == 'remove' %} + Removed {{ serviceName }} + {% else %} + Service updated: {{ serviceName }} + {% endif %} + {% if not loop.last %}
{% endif %} + {% endfor %} + {% endif %} + {% endmacro %} + + {% macro childChangesSummary(childSession) %} + {% set hasChange = false %} +
+ {% if childSession.label %} + {% set hasChange = true %} +
+
Name
+
{{ childSession.label }}
+
+ {% endif %} + {% if childSession.from and childSession.until %} + {% set hasChange = true %} +
+
Time
+
{{ childSession.from | nhsTime }} to {{ childSession.until | nhsTime }}
+
+ {% endif %} + {% if childSession.capacity %} + {% set hasChange = true %} +
+
Capacity
+
{{ childSession.capacity }}
+
+ {% endif %} + {% if childSession.services and (childSession.services | length) > 0 %} + {% set hasChange = true %} +
+
Services
+
{{ childServiceChanges(childSession) }}
+
+ {% endif %} + {% if not hasChange %} +
+
No changes
+
No differences recorded
+
+ {% endif %} +
+ {% endmacro %} + +
+ {% set modelChildSessions = [] %} + {% if data.recurring_sessions and data.recurring_sessions[site_id] and data.recurring_sessions[site_id][editSessionId] %} + {% set modelChildSessions = data.recurring_sessions[site_id][editSessionId].childSessions or [] %} + {% endif %} + {% set childSessions = displaySession.childSessions or modelChildSessions or [] %} + +
+
+ {{ typeLabel(displaySession) }} +

{{ dateLabel(displaySession) }}, {{ timeLabel(displaySession) }}

+
+ +
+
+ {{ services(displaySession.services) }} +
+ {% if childSessions | length > 0 %} +
+ + + + + + + + + + {% for childSession in childSessions %} + + + + + + {% endfor %} + +
DateWhat changedActions
{{ childSession.date | formatDate('d MMM yyyy') }}{{ childChangesSummary(childSession) }}Undo changes
+
+ {% endif %} +
+{% endmacro %} + {% block content %}
@@ -110,28 +221,9 @@

Active clinics

{% endif %} {% if sessionHistory | length > 0 %} - - - - - - - - - - - - {% for session in sessionHistory %} - - - - - - - - {% endfor %} - -
NameTypeDateTimesActions
{{ session.label or 'Clinic' }}{{ typeLabel(session) | safe }}{{ dateLabel(session) }}{{ timeLabel(session) }}Edit
Remove
+ {% for session in sessionHistory %} + {{ clinicCard(session, session.id) }} + {% endfor %} {% endif %}

Completed clinics

From 37c53c9ce1e7111340a405f9e1c8e2a53301df5f Mon Sep 17 00:00:00 2001 From: roobottom Date: Wed, 6 May 2026 11:51:25 +0100 Subject: [PATCH 07/11] Simplify clinics page Co-authored-by: Copilot --- app/assets/sass/components/clinic-card.scss | 119 --------- app/assets/sass/main.scss | 1 - app/routes/base.js | 175 +------------ app/views/site/clinics/clinics.html | 230 +++++------------- .../clinics/edit/child-clinic-overrides.html | 91 ------- 5 files changed, 67 insertions(+), 549 deletions(-) delete mode 100644 app/assets/sass/components/clinic-card.scss delete mode 100644 app/views/site/clinics/edit/child-clinic-overrides.html diff --git a/app/assets/sass/components/clinic-card.scss b/app/assets/sass/components/clinic-card.scss deleted file mode 100644 index 5f65bae..0000000 --- a/app/assets/sass/components/clinic-card.scss +++ /dev/null @@ -1,119 +0,0 @@ -@use "nhsuk-frontend/dist/nhsuk" as *; - -// Support both class names while templates are being refined. -.app-clinic, -.app-clinc { - border: 1px solid $nhsuk-border-colour; - background-color: nhsuk-colour("white"); - @include nhsuk-responsive-margin(4, "bottom"); - - &__header, - .app-clinic__header { - display: flex; - gap: nhsuk-spacing(3); - align-items: flex-start; - justify-content: space-between; - @include nhsuk-responsive-padding(4); - - @include nhsuk-media-query($until: tablet) { - flex-direction: column; - align-items: stretch; - } - } - - &__title, - .app-clinic__title { - .nhsuk-tag { - @include nhsuk-responsive-margin(2, "bottom"); - } - - .nhsuk-heading-xs { - margin-bottom: 0; - } - } - - &__nav, - .app-clinic__nav { - margin-left: auto; - - @include nhsuk-media-query($until: tablet) { - margin-left: 0; - } - } - - &__nav-list, - .app-clinic__nav-list { - margin: 0; - padding: 0; - list-style: none; - display: flex; - gap: nhsuk-spacing(3); - - @include nhsuk-media-query($until: tablet) { - flex-wrap: wrap; - } - } - - &__nav-item, - .app-clinic__nav-item { - margin: 0; - } - - &__content, - .app-clinic__content { - @include nhsuk-font-size(19); - @include nhsuk-responsive-padding(4); - padding-top: 0; - border-bottom: 1px solid $nhsuk-border-colour; - } - - &__footer, - .app-clinic__footer { - @include nhsuk-responsive-padding(4); - padding-top: 0; - padding-bottom: 0; - background-color: nhsuk-colour("grey-5"); - - .nhsuk-body-s { - margin-top: 0; - } - - .app-clinic__changes-table { - margin-bottom: 0; - - .nhsuk-table__header, - .nhsuk-table__cell { - @include nhsuk-font-size(16); - line-height: 1.35; - } - - .nhsuk-table__body .nhsuk-table__row:last-child .nhsuk-table__cell { - border-bottom: 0; - } - } - - .app-clinic__changes-definitions { - margin: 0; - } - - .app-clinic__changes-pair { - @include nhsuk-responsive-margin(2, "bottom"); - - &:last-child { - margin-bottom: 0; - } - } - - .app-clinic__changes-term { - @include nhsuk-font-size(16); - color: $nhsuk-secondary-text-colour; - margin: 0; - } - - .app-clinic__changes-description { - @include nhsuk-font-size(16); - line-height: 1.35; - margin: 0; - } - } -} diff --git a/app/assets/sass/main.scss b/app/assets/sass/main.scss index 04cf372..984c0fe 100755 --- a/app/assets/sass/main.scss +++ b/app/assets/sass/main.scss @@ -4,7 +4,6 @@ @forward "components/app-card"; @forward "components/secondary-navigation"; @forward "components/appointments-summary"; -@forward "components/clinic-card"; diff --git a/app/routes/base.js b/app/routes/base.js index 85fa1c4..f693578 100644 --- a/app/routes/base.js +++ b/app/routes/base.js @@ -57,42 +57,6 @@ function asArray(value) { return [value]; } -function applyServiceOperations(baseServices = [], operations = []) { - let services = [...asArray(baseServices)]; - - for (const change of asArray(operations)) { - if (!change || typeof change !== 'object') continue; - - if (change.operation === 'replace') { - services = asArray(change.values); - continue; - } - - if (change.operation === 'add' && change.service) { - if (!services.includes(change.service)) { - services.push(change.service); - } - continue; - } - - if (change.operation === 'remove' && change.service) { - services = services.filter((service) => service !== change.service); - } - } - - return services; -} - -function sortedStringArray(values = []) { - return asArray(values) - .map((value) => String(value)) - .sort((a, b) => a.localeCompare(b)); -} - -function servicesEqual(left = [], right = []) { - return JSON.stringify(sortedStringArray(left)) === JSON.stringify(sortedStringArray(right)); -} - function normalizeSessionType(type) { if (type === 'Single date') return 'Single clinic'; if (type === 'Weekly session' || type === 'Weekly sessions' || type === 'Weekly repeating') return 'Clinic series'; @@ -444,8 +408,6 @@ function bookingCountText(count) { function resetEditOutcome(state) { state.bookingAction = null; state.affectedBookingIds = []; - state.childOverrideWarning = null; - state.postWarningPath = null; } function editFieldsForStep(step, isSeries) { @@ -925,107 +887,8 @@ function prepareReviewAfterEdit(data, siteId, state) { ? `${editSummaryPath(siteId, state.sessionId)}/affected-bookings` : `${editSummaryPath(siteId, state.sessionId)}/check-answers`; - const warning = buildChildOverrideWarningContext( - state, - originalModel, - updatedModel - ); - - state.childOverrideWarning = warning; - state.postWarningPath = warning ? nextPath : null; - setEditState(data, state); - return warning - ? `${editSummaryPath(siteId, state.sessionId)}/child-clinic-overrides` - : nextPath; -} - -function buildChildOverrideWarningContext(state, originalModel, updatedModel) { - if (state?.draft?.type !== 'Clinic series') return null; - - const editableFields = currentEditableFields(state); - const modelChanges = { - time: String(originalModel?.from || '') !== String(updatedModel?.from || '') - || String(originalModel?.until || '') !== String(updatedModel?.until || ''), - capacity: (Number(originalModel?.capacity) || 1) !== (Number(updatedModel?.capacity) || 1), - services: !servicesEqual(originalModel?.services, updatedModel?.services) - }; - - let targetedFields = ['time', 'capacity', 'services'].filter((field) => editableFields.includes(field)); - if (targetedFields.length === 0) { - targetedFields = Object.entries(modelChanges) - .filter(([, changed]) => changed) - .map(([field]) => field); - } - - if (targetedFields.length === 0) return null; - - const changedFields = targetedFields.filter((field) => modelChanges[field]); - - if (changedFields.length === 0) return null; - - const parentServices = asArray(updatedModel?.services); - const parentLabel = String(updatedModel?.label || '').trim() || 'Clinic series'; - const rows = []; - - for (const child of asArray(updatedModel?.childSessions)) { - if (!child?.date) continue; - - const childHasPairedTime = Boolean(child?.from && child?.until); - const resolvedFrom = childHasPairedTime ? child.from : updatedModel?.from; - const resolvedUntil = childHasPairedTime ? child.until : updatedModel?.until; - const hasTimeOverride = childHasPairedTime - && (String(resolvedFrom) !== String(updatedModel?.from || '') - || String(resolvedUntil) !== String(updatedModel?.until || '')); - - const hasCapacityOverride = child?.capacity !== undefined - && child?.capacity !== null; - - const effectiveServices = child?.services - ? applyServiceOperations(parentServices, child.services) - : [...parentServices]; - const hasServicesOverride = asArray(child?.services).length > 0; - - const include = ( - (changedFields.includes('time') && hasTimeOverride) - || (changedFields.includes('capacity') && hasCapacityOverride) - || (changedFields.includes('services') && hasServicesOverride) - ); - - if (!include) continue; - - rows.push({ - dateISO: child.date, - parentLabel, - childLabel: child?.label || '', - from: resolvedFrom, - until: resolvedUntil, - capacity: Number(child?.capacity ?? updatedModel?.capacity) || 1, - effectiveServiceIds: asArray(effectiveServices) - }); - } - - if (rows.length === 0) return null; - - rows.sort((a, b) => String(a.dateISO).localeCompare(String(b.dateISO))); - - const originalParentServiceIds = sortedStringArray(originalModel?.services); - const updatedParentServiceIds = sortedStringArray(updatedModel?.services); - const parentServicesAddedIds = updatedParentServiceIds - .filter((serviceId) => !originalParentServiceIds.includes(serviceId)); - const parentServicesRemovedIds = originalParentServiceIds - .filter((serviceId) => !updatedParentServiceIds.includes(serviceId)); - - return { - count: rows.length, - hasTimeChange: changedFields.includes('time'), - hasCapacityChange: changedFields.includes('capacity'), - hasServicesChange: changedFields.includes('services'), - parentServicesAddedIds, - parentServicesRemovedIds, - rows, - changedFields - }; + return nextPath; } function calculateAffectedBookings(originalModel, updatedModel, siteBookings) { @@ -1179,7 +1042,6 @@ function buildSessionHistory(siteRecurringSessions, startDate = null, endDate = from: session.from, until: session.until, services: session.services || [], - childSessions: asArray(session.childSessions || []).slice().sort((a, b) => String(a?.date || '').localeCompare(String(b?.date || ''))), capacity: Number(session.capacity) || 0, slotLength: Number(session.slotLength) || 0 }); @@ -1866,41 +1728,6 @@ router.all('/site/:id/clinics/edit/:sessionId/affected-bookings', (req, res) => }); }); -router.all('/site/:id/clinics/edit/:sessionId/child-clinic-overrides', (req, res) => { - const data = req.session.data; - const state = ensureEditStateForSession(data, req.site_id, req.params.sessionId); - if (!state) { - return res.redirect(`/site/${req.site_id}/clinics`); - } - - const warning = state.childOverrideWarning; - const nextPath = state.postWarningPath - || (asArray(state.affectedBookingIds).length > 0 - ? `${editSummaryPath(req.site_id, req.params.sessionId)}/affected-bookings` - : `${editSummaryPath(req.site_id, req.params.sessionId)}/check-answers`); - - if (!warning || !asArray(warning.rows).length) { - return res.redirect(nextPath); - } - - if (req.method === 'POST') { - return res.redirect(nextPath); - } - - const step = state.currentEditStep || editStepForField(state.currentEditField, state?.draft?.type === 'Clinic series'); - const backHref = step - ? editStepPath(req.site_id, req.params.sessionId, step) - : editSummaryPath(req.site_id, req.params.sessionId); - - return res.render('site/clinics/edit/child-clinic-overrides', { - sessionId: req.params.sessionId, - backHref, - formAction: `${editSummaryPath(req.site_id, req.params.sessionId)}/child-clinic-overrides`, - warning: warning, - rows: warning.rows - }); -}); - router.all('/site/:id/clinics/edit/:sessionId/check-answers', (req, res) => { const data = req.session.data; const state = ensureEditStateForSession(data, req.site_id, req.params.sessionId); diff --git a/app/views/site/clinics/clinics.html b/app/views/site/clinics/clinics.html index b99b37e..4e2a747 100644 --- a/app/views/site/clinics/clinics.html +++ b/app/views/site/clinics/clinics.html @@ -8,13 +8,23 @@ {% set serviceTypes = [] %} {% for service_id in service_ids %} - {% set vaccineShortcode = data.services[service_id].cohort.type ~ " " ~ data.services[service_id].vaccine %} - {% if vaccineShortcode not in serviceTypes %} - {% set serviceTypes = serviceTypes.concat([vaccineShortcode]) %} + {% if data.services[service_id] %} + {% set vaccineShortcode = data.services[service_id].cohort.type ~ " " ~ data.services[service_id].vaccine %} + {% if vaccineShortcode not in serviceTypes %} + {% set serviceTypes = serviceTypes.concat([vaccineShortcode]) %} + {% endif %} {% endif %} {% endfor %} - {{ serviceTypes | join(', ') }} + {% if serviceTypes | length > 0 %} +
    + {% for serviceType in serviceTypes %} +
  • {{ serviceType }}
  • + {% endfor %} +
+ {% else %} + No services + {% endif %} {% endmacro %} {% macro timeLabel(session) %} @@ -41,11 +51,6 @@ text: text, classes: "nhsuk-tag--" ~ colour }) }} - -{% endmacro %} - -{% macro daysLabel(session) %} - {{ (session.days or session.recurrencePattern.byDay or []) | shortWeekdays }} {% endmacro %} {% macro dateLabel(session) %} @@ -67,118 +72,35 @@ {% endif %} {% endmacro %} -{% macro clinicCard(displaySession, editSessionId) %} - {% macro childServiceChanges(childSession) %} - {% set operations = childSession.services or [] %} - {% if operations | length == 0 %} - Change services - {% elif operations[0] is string %} - Change services
- Services replaced - {% else %} - {% for operation in operations %} - {% set serviceName = data.services[operation.service].name if data.services[operation.service] else operation.service %} - Change services
- {% if operation.operation == 'add' %} - Added {{ serviceName }} - {% elif operation.operation == 'remove' %} - Removed {{ serviceName }} - {% else %} - Service updated: {{ serviceName }} +{% macro clinicsTable(sessions, includeActions) %} + + + + + + + {% if includeActions %} + {% endif %} - {% if not loop.last %}
{% endif %} + + + + {% for session in sessions %} + + + + + {% if includeActions %} + + {% endif %} + {% endfor %} - {% endif %} - {% endmacro %} - - {% macro childChangesSummary(childSession) %} - {% set hasChange = false %} -
- {% if childSession.label %} - {% set hasChange = true %} -
-
Name
-
{{ childSession.label }}
-
- {% endif %} - {% if childSession.from and childSession.until %} - {% set hasChange = true %} -
-
Time
-
{{ childSession.from | nhsTime }} to {{ childSession.until | nhsTime }}
-
- {% endif %} - {% if childSession.capacity %} - {% set hasChange = true %} -
-
Capacity
-
{{ childSession.capacity }}
-
- {% endif %} - {% if childSession.services and (childSession.services | length) > 0 %} - {% set hasChange = true %} -
-
Services
-
{{ childServiceChanges(childSession) }}
-
- {% endif %} - {% if not hasChange %} -
-
No changes
-
No differences recorded
-
- {% endif %} -
- {% endmacro %} - -
- {% set modelChildSessions = [] %} - {% if data.recurring_sessions and data.recurring_sessions[site_id] and data.recurring_sessions[site_id][editSessionId] %} - {% set modelChildSessions = data.recurring_sessions[site_id][editSessionId].childSessions or [] %} - {% endif %} - {% set childSessions = displaySession.childSessions or modelChildSessions or [] %} - -
-
- {{ typeLabel(displaySession) }} -

{{ dateLabel(displaySession) }}, {{ timeLabel(displaySession) }}

-
- -
-
- {{ services(displaySession.services) }} -
- {% if childSessions | length > 0 %} -
-
TypeDate and timeServicesActions
{{ typeLabel(session) }}{{ dateLabel(session) }}
{{ timeLabel(session) }}
{{ services(session.services) }} + View and edit +
- - - - - - - - - {% for childSession in childSessions %} - - - - - - {% endfor %} - -
DateWhat changedActions
{{ childSession.date | formatDate('d MMM yyyy') }}{{ childChangesSummary(childSession) }}Undo changes
- - {% endif %} -
+ + {% endmacro %} - {% block content %}
@@ -210,55 +132,35 @@

{{ dateLabel(displaySession) }}, {{ timeLabel(displ

{{ pageName }}

-

Active clinics

- {% set sessionHistory = sessionHistory or [] %} - - {% if sessionHistory | length == 0 %} - {{ insetText({ - text: "No clinics have been created yet." - }) }} - {% endif %} - - {% if sessionHistory | length > 0 %} - {% for session in sessionHistory %} - {{ clinicCard(session, session.id) }} - {% endfor %} - {% endif %} - -

Completed clinics

- {% set pastSessionHistory = pastSessionHistory or [] %} - {% if pastSessionHistory | length == 0 %} - {{ insetText({ - text: "No completed clinics" - }) }} - {% endif %} - - {% if pastSessionHistory | length > 0 %} - - - - - - - - - - - {% for session in pastSessionHistory %} - - - - - - - {% endfor %} - -
NameTypeDateTimes
{{ session.label or 'Clinic' }}{{ typeLabel(session) }}{{ dateLabel(session) }}{{ timeLabel(session) }}
- {% endif %} - + {{ tabs({ + items: [ + { + label: "Active clinics (" ~ (sessionHistory | length) ~ ")", + id: "active-clinics", + panel: { + html: ( + insetText({ text: "No clinics have been created yet." }) + if (sessionHistory | length) == 0 + else clinicsTable(sessionHistory, true) + ) + } + }, + { + label: "Completed clinics (" ~ (pastSessionHistory | length) ~ ")", + id: "completed-clinics", + panel: { + html: ( + insetText({ text: "No completed clinics" }) + if (pastSessionHistory | length) == 0 + else clinicsTable(pastSessionHistory, false) + ) + } + } + ] + }) }}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/views/site/clinics/edit/child-clinic-overrides.html b/app/views/site/clinics/edit/child-clinic-overrides.html deleted file mode 100644 index c8f26e6..0000000 --- a/app/views/site/clinics/edit/child-clinic-overrides.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends '../../../layouts/layout.html' %} - -{% block beforeContent %} -{{ backLink({ - href: backHref -}) }} -{% endblock %} - -{% block content %} -
-
- {% set clinicWord = 'clinic' if warning.count == 1 else 'clinics' %} - {% set pronoun = 'this clinic' if warning.count == 1 else 'these clinics' %} - - {% set headingPrefix = 'Clinics' %} - {% if warning.hasTimeChange and not warning.hasCapacityChange and not warning.hasServicesChange %} - {% set headingPrefix = 'Clinic times' %} - {% elif warning.hasCapacityChange and not warning.hasTimeChange and not warning.hasServicesChange %} - {% set headingPrefix = 'Number of vaccinators' %} - {% elif warning.hasServicesChange and not warning.hasTimeChange and not warning.hasCapacityChange %} - {% set headingPrefix = 'Services' %} - {% endif %} - - Edit clinic series -

{{ warning.count }} {{ clinicWord }} in this series will not be updated

- - {% if warning.hasTimeChange %} -

You already edited the times for {{ pronoun }}. The start and end times for {{ pronoun }} will not be updated as part of the changes to the series.

- {% endif %} - - {% if warning.hasCapacityChange %} -

You already edited the number of vaccinators for {{ pronoun }}. The number of vaccinators for {{ pronoun }} will not be updated as part of the changes to the series.

- {% endif %} - - {% call details({ - summaryText: "View clinics that will not be updated", - classes: "nhsuk-expander" - }) %} - - - - - {% if warning.hasTimeChange %} - - {% endif %} - {% if warning.hasCapacityChange %} - - {% endif %} - {% if warning.hasServicesChange %} - - {% endif %} - - - - {% for row in rows %} - - - {% if warning.hasTimeChange %} - - {% endif %} - {% if warning.hasCapacityChange %} - - {% endif %} - {% if warning.hasServicesChange %} - - {% endif %} - - {% endfor %} - -
DateTimesVaccinatorsServices this clinic will have
- {{ row.dateISO | nhsDate }} - {{ row.from }} to {{ row.until }}{{ row.capacity }} - {% if row.effectiveServiceIds and row.effectiveServiceIds.length %} -
    - {% for serviceId in row.effectiveServiceIds %} -
  • {{ data.services[serviceId].name if data.services[serviceId] else serviceId }}
  • - {% endfor %} -
- {% else %} - None selected - {% endif %} -
- {% endcall %} - - -
- {{ button({ text: 'Continue' }) }} -
-
-
-{% endblock %} From 733f488aa61ec8f40673c8f5d4e7f4318527732d Mon Sep 17 00:00:00 2001 From: roobottom Date: Wed, 6 May 2026 11:59:01 +0100 Subject: [PATCH 08/11] remove name from UI Co-authored-by: Copilot --- app/routes/base.js | 16 +++------------- app/views/site/availability/day.html | 9 +-------- app/views/site/availability/week.html | 5 +---- app/views/site/change-session/summary.html | 15 --------------- app/views/site/clinics/edit/summary.html | 4 ---- app/views/site/clinics/series/check-answers.html | 16 ---------------- .../clinics/series/clinic-closures-form.html | 2 +- app/views/site/clinics/series/details.html | 14 -------------- app/views/site/clinics/single/check-answers.html | 16 ---------------- app/views/site/clinics/single/details.html | 14 -------------- 10 files changed, 6 insertions(+), 105 deletions(-) diff --git a/app/routes/base.js b/app/routes/base.js index f693578..7aa8a8f 100644 --- a/app/routes/base.js +++ b/app/routes/base.js @@ -368,7 +368,6 @@ function clone(value) { function editFieldOptions(isSeries) { const options = [ - { value: 'name', text: 'Name' }, { value: 'date', text: isSeries ? 'Dates' : 'Date' }, ...(isSeries ? [{ value: 'days', text: 'Days' }] : []), { value: 'time', text: 'Time' }, @@ -413,7 +412,7 @@ function resetEditOutcome(state) { function editFieldsForStep(step, isSeries) { switch (step) { case 'details': - return ['name', 'date']; + return ['date']; case 'days': return isSeries ? ['days'] : ['date']; case 'clinic-times': @@ -470,7 +469,6 @@ function editCaptionText(draft) { function editStepForField(field, isSeries) { switch (field) { - case 'name': case 'date': return 'details'; case 'days': @@ -499,7 +497,7 @@ function setEditTemplateData(res, data, state) { function updateDraftFromDetails(state, newSession = {}) { const editableFields = currentEditableFields(state); - if (editableFields.length === 0 || editableFields.includes('name')) { + if ((editableFields.length === 0 || editableFields.includes('name')) && newSession.name !== undefined) { state.draft.name = String(newSession.name || '').trim(); } @@ -729,11 +727,6 @@ function buildSummaryRowMap(draft, siteId, sessionId, data) { const closuresText = draft.closures?.length ? `${draft.closures.length} added` : 'None added'; return { - name: { - key: { text: 'Name' }, - value: { text: draft.name || 'Not provided' }, - actions: { items: [{ href: `/site/${siteId}/clinics/edit/${sessionId}/change/name`, text: 'Change', visuallyHiddenText: ' name' }] } - }, date: { key: { text: isSeries ? 'Dates' : 'Date' }, value: { text: dateText }, @@ -777,7 +770,6 @@ function buildSummaryRowMap(draft, siteId, sessionId, data) { function buildSummaryRowsForEdit(draft, siteId, sessionId, data) { const rowsByField = buildSummaryRowMap(draft, siteId, sessionId, data); const orderedFields = [ - 'name', 'date', 'days', 'time', @@ -794,8 +786,6 @@ function buildSummaryRowsForEdit(draft, siteId, sessionId, data) { function hasEditFieldChanged(original, draft, field) { switch (field) { - case 'name': - return String(original?.label || '') !== String(draft?.name || ''); case 'date': if (draft?.type === 'Clinic series') { return String(original?.startDate || '') !== String(draft?.startDate || '') @@ -838,7 +828,7 @@ function buildChangedRowsForEdit(original, draft, state, siteId, sessionId, data const editableFields = currentEditableFields(state); const candidateFields = editableFields.length > 0 ? editableFields - : ['name', 'date', 'days', 'time', 'capacity', 'duration', 'services', 'closures']; + : ['date', 'days', 'time', 'capacity', 'duration', 'services', 'closures']; const changedRows = candidateFields .filter((field) => rowsByField[field] && hasEditFieldChanged(original, draft, field)) diff --git a/app/views/site/availability/day.html b/app/views/site/availability/day.html index b8d0438..c33b897 100644 --- a/app/views/site/availability/day.html +++ b/app/views/site/availability/day.html @@ -78,10 +78,6 @@

{{ opts.title }}

{% for slot in slots[date] %} {% if slot.booking_status == opts.status or slot.booking_status in opts.status %} {% set booking = data.bookings[site_id][slot.booking_id] %} - {% set clinicName = 'Clinic' %} - {% if data.recurring_sessions and data.recurring_sessions[site_id] and slot.recurringSessionId and data.recurring_sessions[site_id][slot.recurringSessionId] %} - {% set clinicName = data.recurring_sessions[site_id][slot.recurringSessionId].label %} - {% endif %} {{ booking.datetime | formatDate('h:mm a') | lower }} @@ -121,7 +117,7 @@

{{ opts.title }}

- + @@ -132,9 +128,6 @@

{{ opts.title }}

{% for session in sessions %}
Name and timeTime Services Booked Unbooked
- {% if session.label %} - {{ session.label }}
- {% endif %} {{ session.from | nhsTime }} to {{ session.until | nhsTime }}
diff --git a/app/views/site/availability/week.html b/app/views/site/availability/week.html index 76b0ab7..048ba8c 100644 --- a/app/views/site/availability/week.html +++ b/app/views/site/availability/week.html @@ -60,7 +60,7 @@

{{ pageName }}

- + @@ -71,9 +71,6 @@

{{ pageName }}

{% for session in day.sessions %} - + {% if includeActions %} @@ -88,7 +88,7 @@ {% for session in sessions %} - + {% if includeActions %}
Name and timeTime Services Booked Unbooked
- {% if session.label %} - {{ session.label }}
- {% endif %} {{ session.from | nhsTime }} to {{ session.until | nhsTime }}
diff --git a/app/views/site/change-session/summary.html b/app/views/site/change-session/summary.html index a00e3db..7332d2b 100644 --- a/app/views/site/change-session/summary.html +++ b/app/views/site/change-session/summary.html @@ -31,21 +31,6 @@ {{ displayDate }}

{{ pageName }}

- {{ appCard({ - title: 'Clinic details', - action: { - href: changeBasePath ~ '/details', - text: 'Change', - visuallyHiddenText: ' clinic name' - }, - items: [ - { - key: 'Clinic name', - value: draft.name or 'Not provided' - } - ] - }) }} - {{ appCard({ title: 'Capacity calculation', action: { diff --git a/app/views/site/clinics/edit/summary.html b/app/views/site/clinics/edit/summary.html index a371212..dec20e4 100644 --- a/app/views/site/clinics/edit/summary.html +++ b/app/views/site/clinics/edit/summary.html @@ -74,10 +74,6 @@

{{ pageName }}

visuallyHiddenText: ' clinic details' }, items: [ - { - key: 'Clinic name', - value: draft.name or 'Not provided' - }, { key: 'Start date', value: draft.startDate | nhsDate diff --git a/app/views/site/clinics/series/check-answers.html b/app/views/site/clinics/series/check-answers.html index 2baa14a..056c3fd 100644 --- a/app/views/site/clinics/series/check-answers.html +++ b/app/views/site/clinics/series/check-answers.html @@ -101,22 +101,6 @@

{{ pageName }}

{{ summaryList({ rows: [ { - key: { - text: "Name" - }, - value: { - text: data.newSession.name or "Not provided" - }, - actions: { - items: [ - { - href: "/site/" + site_id + "/clinics/details", - text: "Change", - visuallyHiddenText: " clinic name" - } - ] - } - }, { key: { text: "Dates" }, diff --git a/app/views/site/clinics/series/clinic-closures-form.html b/app/views/site/clinics/series/clinic-closures-form.html index bfd1010..9e6ff82 100644 --- a/app/views/site/clinics/series/clinic-closures-form.html +++ b/app/views/site/clinics/series/clinic-closures-form.html @@ -32,7 +32,7 @@

There is a probl {{ input({ label: { - text: "Name", + text: "Closure label", classes: "nhsuk-label--m" }, id: "closure-name", diff --git a/app/views/site/clinics/series/details.html b/app/views/site/clinics/series/details.html index 6fe3821..949e02c 100644 --- a/app/views/site/clinics/series/details.html +++ b/app/views/site/clinics/series/details.html @@ -18,20 +18,6 @@

{{ pageName }}

- {{ input({ - label: { - text: "Series name", - classes: "nhsuk-label--m" - }, - id: "clinic-name", - name: "newSession[name]", - classes: "nhsuk-input--width-20", - hint: { - text: "A short name to help you identify this clinic series. For example, weekly flu clinic" - }, - value: data.newSession.name - }) }} - {% if not hideDateFields %} {{ dateInput({ id: "start-date", diff --git a/app/views/site/clinics/single/check-answers.html b/app/views/site/clinics/single/check-answers.html index 6683bce..4ea7a5c 100644 --- a/app/views/site/clinics/single/check-answers.html +++ b/app/views/site/clinics/single/check-answers.html @@ -69,22 +69,6 @@

{{ pageName }}

{{ summaryList({ rows: [ { - key: { - text: "Name" - }, - value: { - text: data.newSession.name or "Not provided" - }, - actions: { - items: [ - { - href: "/site/" + site_id + "/clinics/details", - text: "Change", - visuallyHiddenText: " clinic name" - } - ] - } - }, { key: { text: "Date" }, diff --git a/app/views/site/clinics/single/details.html b/app/views/site/clinics/single/details.html index e289096..cbf8c2e 100644 --- a/app/views/site/clinics/single/details.html +++ b/app/views/site/clinics/single/details.html @@ -18,20 +18,6 @@

{{ pageName }}

- {{ input({ - label: { - text: "Clinic name", - classes: "nhsuk-label--m" - }, - id: "clinic-name", - name: "newSession[name]", - classes: "nhsuk-input--width-20", - hint: { - text: "A short name to help you identify this clinic series. For example, weekly flu clinic" - }, - value: data.newSession.name - }) }} - {{ dateInput({ id: "single-date", namePrefix: "newSession[singleDate]", From 5b3fe7fef2cc320fce5ff9bcbf33ccb8b897f8ec Mon Sep 17 00:00:00 2001 From: roobottom Date: Wed, 6 May 2026 12:03:56 +0100 Subject: [PATCH 09/11] updated labels and h1 position --- app/views/site/availability/day.html | 5 ++--- app/views/site/availability/month.html | 5 ++--- app/views/site/availability/week.html | 6 +++--- app/views/site/clinics/clinics.html | 8 ++++---- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/views/site/availability/day.html b/app/views/site/availability/day.html index c33b897..678db21 100644 --- a/app/views/site/availability/day.html +++ b/app/views/site/availability/day.html @@ -25,15 +25,14 @@ href: '/site/' ~ site_id ~ '/availability/month' }, { - text: 'All clinics', + text: 'Clinics list', href: '/site/' ~ site_id ~ '/clinics' } ] }) }} - - {{ appActionControlBar({ siteId: site_id, print: '#' }) }}

{{ pageName }}

+ {{ appActionControlBar({ siteId: site_id, print: '#' }) }} {{ pagination({ previous: { diff --git a/app/views/site/availability/month.html b/app/views/site/availability/month.html index 8e09756..7501d00 100644 --- a/app/views/site/availability/month.html +++ b/app/views/site/availability/month.html @@ -25,16 +25,15 @@ current: true }, { - text: 'All clinics', + text: 'Clinics list', href: '/site/' ~ site_id ~ '/clinics' } ] }) }} - {{ appActionControlBar({ siteId: site_id }) }} -

{{ pageName }}

+ {{ appActionControlBar({ siteId: site_id }) }} {{ pagination({ previous: { diff --git a/app/views/site/availability/week.html b/app/views/site/availability/week.html index 048ba8c..f5417f3 100644 --- a/app/views/site/availability/week.html +++ b/app/views/site/availability/week.html @@ -25,16 +25,16 @@ href: '/site/' ~ site_id ~ '/availability/month' }, { - text: 'All clinics', + text: 'Clinics list', href: '/site/' ~ site_id ~ '/clinics' } ] }) }} - +

{{ pageName }}

{{ appActionControlBar({ siteId: site_id }) }} -

{{ pageName }}

+ {{ pagination({ previous: { diff --git a/app/views/site/clinics/clinics.html b/app/views/site/clinics/clinics.html index 4e2a747..1867ab8 100644 --- a/app/views/site/clinics/clinics.html +++ b/app/views/site/clinics/clinics.html @@ -2,7 +2,7 @@ {% from '../../components/secondary-navigation.njk' import appSecondaryNavigation %} {% from '../../components/action-control-bar.njk' import appActionControlBar %} -{% set pageName = "Clinics" %} +{% set pageName = "Clinics list" %} {% macro services(service_ids) %} {% set serviceTypes = [] %} @@ -120,17 +120,17 @@ href: '/site/' ~ site_id ~ '/availability/month' }, { - text: 'All clinics', + text: 'Clinics list', href: '/site/' ~ site_id ~ '/clinics', current: true } ] }) }} - +

{{ pageName }}

{{ appActionControlBar({ siteId: site_id }) }} -

{{ pageName }}

+ {% set sessionHistory = sessionHistory or [] %} {% set pastSessionHistory = pastSessionHistory or [] %} From e1a61d5c348c353c093466a5222dd864cd91f248 Mon Sep 17 00:00:00 2001 From: roobottom Date: Wed, 6 May 2026 15:03:44 +0100 Subject: [PATCH 10/11] added child items to clinic summary Co-authored-by: Copilot --- app/assets/sass/components/app-card.scss | 45 ++++------- app/data/session-data-defaults.js | 2 +- app/data/site1.config.js | 39 ++++++++++ app/views/components/app-card.njk | 42 ++++++----- app/views/site/clinics/clinics.html | 4 +- app/views/site/clinics/edit/summary.html | 96 ++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 52 deletions(-) diff --git a/app/assets/sass/components/app-card.scss b/app/assets/sass/components/app-card.scss index 79ae51a..d88c122 100644 --- a/app/assets/sass/components/app-card.scss +++ b/app/assets/sass/components/app-card.scss @@ -4,6 +4,8 @@ border: 1px solid $nhsuk-border-colour; background-color: nhsuk-colour("white"); @include nhsuk-responsive-margin(4, bottom); + //NHS looking shadow + box-shadow: 0 2px 0 0 nhsuk-colour("grey-4"); &__header { display: flex; @@ -13,6 +15,7 @@ padding: nhsuk-spacing(3); background-color: nhsuk-colour("grey-5"); border-bottom: 1px solid $nhsuk-border-colour; + } &__title { @@ -22,45 +25,29 @@ } &__action { - @include nhsuk-font-size(14); + @include nhsuk-font-size(16); white-space: nowrap; } &__body { - padding: 0 nhsuk-spacing(3); - } - - &__table { - width: 100%; - margin: 0; - border-collapse: collapse; + padding: nhsuk-spacing(3); } - &__row { - border-bottom: 1px solid nhsuk-colour("grey-5"); + &__summary-list { + margin-bottom: 0; - &:last-child th, - &:last-child td { - border-bottom: 0; + .nhsuk-summary-list__key, + .nhsuk-summary-list__value, + .nhsuk-summary-list__actions { + @include nhsuk-font-size(16); + vertical-align: top; } - } - &__key, - &__value { - @include nhsuk-font-size(16); - padding: nhsuk-spacing(3) 0; - vertical-align: top; - } - - &__key { - width: 34%; - padding-right: nhsuk-spacing(3); - font-weight: $nhsuk-font-bold; - text-align: left; - } + .nhsuk-summary-list__row:last-child { + border-bottom: none; + } - &__value { - > :last-child { + .nhsuk-summary-list__value > :last-child { margin-bottom: 0; } } diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index 4534138..e8f6a31 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -54,7 +54,7 @@ const serviceDefinitions = [ { id: 'RSV:Adult', name: 'RSV Adult', vaccine: vaccineTypes.RSV, group: 'RSV', age: '18+', type: 'Adult' }, { id: 'RSV_COVID:12-17', name: 'RSV and COVID 12 to 17', vaccine: [vaccineTypes.RSV, vaccineTypes.COVID], group: 'RSV_AND_COVID', age: '12-17', type: 'Child' }, { id: 'RSV_COVID:18+', name: 'RSV and COVID 18+', vaccine: [vaccineTypes.RSV, vaccineTypes.COVID], group: 'RSV_AND_COVID', age: '18+', type: 'Adult' }, - { id: 'MENB:16-18', name: 'MenB 17 to 18', vaccine: vaccineTypes.MENB, group: 'MENB', age: '17-18', type: null } + { id: 'MENB:16-18', name: 'MenB Young People', vaccine: vaccineTypes.MENB, group: 'MENB', age: '17-18', type: null } ]; const SERVICES = Object.fromEntries( diff --git a/app/data/site1.config.js b/app/data/site1.config.js index a42963c..6350718 100644 --- a/app/data/site1.config.js +++ b/app/data/site1.config.js @@ -11,6 +11,10 @@ const ongoingSeriesEnd = today.plus({ months: 2 }).toISODate(); const futureSingleDate = today.plus({ days: 3 }).toISODate(); const childSessionIn2Days = today.plus({ days: 2 }).toISODate(); const childSessionIn3Days = today.plus({ days: 3 }).toISODate(); +const childSessionIn4Days = today.plus({ days: 4 }).toISODate(); +const childSessionIn5Days = today.plus({ days: 5 }).toISODate(); +const childSessionIn6Days = today.plus({ days: 6 }).toISODate(); +const childSessionIn7Days = today.plus({ days: 7 }).toISODate(); const nextTuesdayDate = today.plus({ days: (2 - today.weekday + 7) % 7 }).toISODate(); const SERVICE_IDS = { @@ -106,6 +110,41 @@ const clinics = [ from: '10:00', until: '16:30', label: 'Adult Flu and Covid clinics (extended)' + }, + { + date: childSessionIn4Days, + capacity: 3, + from: '10:00', + until: '16:30', + label: 'Adult Flu and Covid clinics (extra vaccinator and change in times)' + }, + { + date: childSessionIn5Days, + services: [ + SERVICE_IDS.COVID_ADULT, + SERVICE_IDS.RSV_ADULT + ], + label: 'Adult Flu and Covid clinics (service replace)' + }, + { + date: childSessionIn6Days, + services: [ + { + operation: 'add', + service: SERVICE_IDS.RSV_ADULT + } + ], + label: 'Adult Flu and Covid clinics (service add)' + }, + { + date: childSessionIn7Days, + services: [ + { + operation: 'remove', + service: SERVICE_IDS.FLU_65_PLUS + } + ], + label: 'Adult Flu and Covid clinics (service remove)' } ], closures: [ diff --git a/app/views/components/app-card.njk b/app/views/components/app-card.njk index 683bc8c..9576df4 100644 --- a/app/views/components/app-card.njk +++ b/app/views/components/app-card.njk @@ -1,4 +1,5 @@ {%- from "nhsuk/macros/attributes.njk" import nhsukAttributes -%} +{%- from "nhsuk/components/summary-list/macro.njk" import summaryList -%} {%- macro appCard(params) -%}
{%- if params.title or params.action %} @@ -22,30 +23,31 @@ {%- if params.html %} {{ params.html | safe }} {%- elif params.items %} - - + {% set rows = [] %} {%- for item in params.items %} {%- if item %} - - - - + {% set rowKey = { text: item.key } %} + {% if item.keyHtml %} + {% set rowKey = { html: item.keyHtml } %} + {% endif %} + + {% set rowValue = { text: item.value } %} + {% if item.html %} + {% set rowValue = { html: item.html } %} + {% endif %} + + {% set rows = rows.concat([{ + key: rowKey, + value: rowValue, + actions: item.actions + }]) %} {%- endif %} {%- endfor %} - -
- {%- if item.keyHtml %} - {{ item.keyHtml | safe }} - {%- else %} - {{ item.key }} - {%- endif %} - - {%- if item.html %} - {{ item.html | safe }} - {%- else %} - {{ item.value }} - {%- endif %} -
+ + {{ summaryList({ + classes: 'app-card__summary-list', + rows: rows + }) }} {%- endif %}
diff --git a/app/views/site/clinics/clinics.html b/app/views/site/clinics/clinics.html index 1867ab8..6d00575 100644 --- a/app/views/site/clinics/clinics.html +++ b/app/views/site/clinics/clinics.html @@ -77,7 +77,7 @@
TypeDate and timeTime and date ServicesActions
{{ typeLabel(session) }}{{ dateLabel(session) }}
{{ timeLabel(session) }}
{{ timeLabel(session) }}
{{ dateLabel(session) }}
{{ services(session.services) }} diff --git a/app/views/site/clinics/edit/summary.html b/app/views/site/clinics/edit/summary.html index dec20e4..9cb3d9d 100644 --- a/app/views/site/clinics/edit/summary.html +++ b/app/views/site/clinics/edit/summary.html @@ -10,6 +10,54 @@ {% block content %}
+ {% macro oneOffChangesSummary(childSession) %} + {% set hasChange = false %} +
+ {% if childSession.from and childSession.until %} + {% set hasChange = true %} +
Times
+
{{ childSession.from | nhsTime }} to {{ childSession.until | nhsTime }}
+ {% endif %} + + {% if childSession.capacity %} + {% set hasChange = true %} +
Vaccinators
+
{{ childSession.capacity }}
+ {% endif %} + + {% if childSession.services and (childSession.services | length) > 0 %} + {% set hasChange = true %} +
Services
+
+ {% if childSession.services[0] is string %} + {% for serviceId in childSession.services %} + {{ data.services[serviceId].name if data.services[serviceId] else serviceId }}{% if not loop.last %}
{% endif %} + {% endfor %} + {% else %} + {% for operation in childSession.services %} + {% set serviceName = data.services[operation.service].name if data.services[operation.service] else operation.service %} + {% if operation.operation == 'add' %} + Added {{ serviceName }} + {% elif operation.operation == 'remove' %} + Removed {{ serviceName }} + {% elif operation.operation == 'replace' %} + Services replaced + {% else %} + Updated {{ serviceName }} + {% endif %} + {% if not loop.last %}
{% endif %} + {% endfor %} + {% endif %} +
+ {% endif %} + + {% if not hasChange %} +
No changes
+
No one-off changes recorded
+ {% endif %} +
+ {% endmacro %} + {% set editBasePath = '/site/' ~ site_id ~ '/clinics/edit/' ~ sessionId %} {% set isSeries = draft.type == 'Clinic series' %} {% set pageName = 'Edit clinic series' if isSeries else 'Edit single clinic' %} @@ -171,6 +219,54 @@

{{ pageName }}

}, items: closuresItems }) }} + + {% set oneOffChangesItems = [] %} + {% if draft.childSessions and draft.childSessions.length %} + {% for childSession in draft.childSessions %} + {% set childDate = childSession.date | formatDate('d MMM yyyy') %} + {% set childInstanceId = '' %} + {% set daySessions = dailyAvailability[childSession.date].sessions if dailyAvailability and dailyAvailability[childSession.date] and dailyAvailability[childSession.date].sessions else [] %} + {% for daySession in daySessions %} + {% if daySession.recurringId == sessionId and not childInstanceId %} + {% set childInstanceId = daySession.id %} + {% endif %} + {% endfor %} + + {% set childChangeHref = editBasePath %} + {% if childInstanceId %} + {% set backHref = editBasePath %} + {% set childChangeHref = '/site/' + site_id + '/change/session/' + childInstanceId + '?back=' + (backHref | urlencode) %} + {% endif %} + + {% set oneOffChangesItems = oneOffChangesItems.concat([ + { + key: childDate, + html: oneOffChangesSummary(childSession), + actions: { + items: [ + { + href: childChangeHref, + text: 'Change', + visuallyHiddenText: ' one-off changes for ' + childDate + } + ] + } + } + ]) %} + {% endfor %} + {% endif %} + + {% if oneOffChangesItems | length > 0 %} + {{ appCard({ + title: 'Clinics with changes', + items: oneOffChangesItems + }) }} + {% else %} + {{ appCard({ + title: 'Clinics with changes', + html: '

No clinics with one-off changes.

' + }) }} + {% endif %} {% endif %}
From 3ed8b30e05ff65d769eb2acc68cdb1a295b0a678 Mon Sep 17 00:00:00 2001 From: roobottom Date: Thu, 7 May 2026 12:45:15 +0100 Subject: [PATCH 11/11] success page updates Co-authored-by: Copilot --- app/data/site1.config.js | 17 +-- app/routes/base.js | 83 +++++++++-- app/routes/change-session.js | 27 ++-- .../site/clinics/edit/check-answers.html | 141 +++++++++++++++++- app/views/site/clinics/edit/success.html | 50 +++++-- app/views/site/clinics/edit/summary.html | 127 +++++++++++----- app/views/site/clinics/series/success.html | 3 +- app/views/site/clinics/single/success.html | 3 +- 8 files changed, 353 insertions(+), 98 deletions(-) diff --git a/app/data/site1.config.js b/app/data/site1.config.js index 6350718..a3050a9 100644 --- a/app/data/site1.config.js +++ b/app/data/site1.config.js @@ -105,26 +105,11 @@ const clinics = [ until: '16:00', label: 'Adult Flu and Covid clinics (extended)' }, - { - date: childSessionIn3Days, - from: '10:00', - until: '16:30', - label: 'Adult Flu and Covid clinics (extended)' - }, { date: childSessionIn4Days, - capacity: 3, from: '10:00', until: '16:30', - label: 'Adult Flu and Covid clinics (extra vaccinator and change in times)' - }, - { - date: childSessionIn5Days, - services: [ - SERVICE_IDS.COVID_ADULT, - SERVICE_IDS.RSV_ADULT - ], - label: 'Adult Flu and Covid clinics (service replace)' + label: 'Adult Flu and Covid clinics (extended)' }, { date: childSessionIn6Days, diff --git a/app/routes/base.js b/app/routes/base.js index 7aa8a8f..e4554fd 100644 --- a/app/routes/base.js +++ b/app/routes/base.js @@ -823,28 +823,64 @@ function hasEditFieldChanged(original, draft, field) { } } -function buildChangedRowsForEdit(original, draft, state, siteId, sessionId, data) { - const rowsByField = buildSummaryRowMap(draft, siteId, sessionId, data); +function buildChangedFieldKeysForEdit(original, draft, state) { const editableFields = currentEditableFields(state); const candidateFields = editableFields.length > 0 ? editableFields : ['date', 'days', 'time', 'capacity', 'duration', 'services', 'closures']; - const changedRows = candidateFields - .filter((field) => rowsByField[field] && hasEditFieldChanged(original, draft, field)) - .map((field) => rowsByField[field]); + const changedFields = candidateFields + .filter((field) => hasEditFieldChanged(original, draft, field)); - if (changedRows.length > 0) { - return changedRows; + if (changedFields.length > 0) { + return changedFields; } if (editableFields.length > 0) { - return editableFields - .filter((field) => rowsByField[field]) - .map((field) => rowsByField[field]); + return editableFields; } - return buildSummaryRowsForEdit(draft, siteId, sessionId, data); + return candidateFields; +} + +function childSessionUnaffectedByField(childSession, field) { + switch (field) { + case 'time': + return Boolean(childSession?.from && childSession?.until); + case 'capacity': + return childSession?.capacity !== undefined && childSession?.capacity !== null; + case 'services': + return asArray(childSession?.services).length > 0; + default: + return false; + } +} + +function formatShortDate(dateISO) { + const dt = DateTime.fromISO(dateISO || ''); + return dt.isValid ? dt.toFormat('d MMM yyyy') : String(dateISO || ''); +} + +function buildUnaffectedChildClinicDates(model, changedFields, siteId) { + const childSessions = asArray(model?.childSessions).filter((childSession) => childSession?.date); + if (childSessions.length === 0) return []; + + const changed = asArray(changedFields).filter(Boolean); + if (changed.length === 0) return []; + + const merged = mergeDailyAvailability({}, String(siteId || ''), { [model.id]: model }); + + const unaffectedDates = childSessions + .filter((childSession) => changed.every((field) => childSessionUnaffectedByField(childSession, field))) + .map((childSession) => childSession.date) + .filter((dateISO) => { + const sessions = merged?.[dateISO]?.sessions || []; + return sessions.some((session) => String(session?.recurringId) === String(model?.id)); + }); + + return Array.from(new Set(unaffectedDates)) + .sort((a, b) => String(a).localeCompare(String(b))) + .map((dateISO) => formatShortDate(dateISO)); } function reviewBackPath(siteId, sessionId, state) { @@ -1727,6 +1763,10 @@ router.all('/site/:id/clinics/edit/:sessionId/check-answers', (req, res) => { if (req.method === 'POST') { const updatedModel = draftToModel(state.draft); + const changedFields = buildChangedFieldKeysForEdit(state.original, state.draft, state); + const unaffectedChildClinics = state.draft.type === 'Clinic series' + ? buildUnaffectedChildClinicDates(updatedModel, changedFields, req.site_id) + : []; persistRecurringSession(data, req.site_id, updatedModel); const siteBookings = data?.bookings?.[req.site_id] || {}; @@ -1742,7 +1782,8 @@ router.all('/site/:id/clinics/edit/:sessionId/check-answers', (req, res) => { siteId: req.site_id, sessionId: req.params.sessionId, isSeries: state.draft.type === 'Clinic series', - cancelledBookingsSummary + cancelledBookingsSummary, + unaffectedChildClinics }); applyAffectedBookingAction(siteBookings, state.affectedBookingIds, state.bookingAction); @@ -1753,7 +1794,20 @@ router.all('/site/:id/clinics/edit/:sessionId/check-answers', (req, res) => { return res.render('site/clinics/edit/check-answers', { sessionId: req.params.sessionId, isSeries: state.draft.type === 'Clinic series', - rows: buildChangedRowsForEdit(state.original, state.draft, state, req.site_id, req.params.sessionId, data), + rowFields: buildChangedFieldKeysForEdit(state.original, state.draft, state), + draft: state.draft, + previous: { + startDate: state.original?.startDate, + endDate: state.original?.endDate, + days: asArray(state.original?.recurrencePattern?.byDay), + from: state.original?.from, + until: state.original?.until, + capacity: String(Number(state.original?.capacity) || 1), + duration: String(Number(state.original?.slotLength) || 10), + services: asArray(state.original?.services), + closuresCount: asArray(state.original?.closures).length + }, + checkAnswersMode: 'clinic-edit', affectedCount: asArray(state.affectedBookingIds).length, bookingAction: state.bookingAction, backHref: reviewBackPath(req.site_id, req.params.sessionId, state) @@ -1789,7 +1843,8 @@ router.get('/site/:id/clinics/edit/:sessionId/success', (req, res) => { return res.render('site/clinics/edit/success', { sessionId: req.params.sessionId, - cancelSummary + cancelSummary, + unaffectedChildClinics: matchingSuccessState?.unaffectedChildClinics || [] }); }); diff --git a/app/routes/change-session.js b/app/routes/change-session.js index 5e87732..0e23807 100644 --- a/app/routes/change-session.js +++ b/app/routes/change-session.js @@ -373,20 +373,16 @@ function hasFieldChanged(state, field) { } } -function buildChangedRowsForCheckAnswers(state, siteId, itemId, data) { - const rowMap = buildSummaryRowMap(state.draft, siteId, itemId, data); +function buildChangedFieldKeysForCheckAnswers(state) { const editableFields = currentEditableFields(state); - const rows = editableFields - .filter((field) => rowMap[field] && hasFieldChanged(state, field)) - .map((field) => rowMap[field]); + const changedFields = editableFields + .filter((field) => hasFieldChanged(state, field)); - if (rows.length > 0) { - return rows; + if (changedFields.length > 0) { + return changedFields; } - return editableFields - .filter((field) => rowMap[field]) - .map((field) => rowMap[field]); + return editableFields; } function normalizeIsoMinute(datetimeISO) { @@ -814,7 +810,16 @@ router.all('/site/:id/change/session/:itemId/check-answers', (req, res) => { pageName: 'Check your answers', sessionId: req.params.itemId, isSeries: false, - rows: buildChangedRowsForCheckAnswers(state, req.site_id, req.params.itemId, data), + rowFields: buildChangedFieldKeysForCheckAnswers(state), + draft: state.draft, + previous: { + name: state.draft.parentName, + from: state.draft.parentFrom, + until: state.draft.parentUntil, + capacity: String(state.draft.parentCapacity), + services: asArray(state.draft.parentServices) + }, + checkAnswersMode: 'session-change', affectedCount: asArray(state.affectedBookingIds).length, bookingAction: state.bookingAction, formAction: `${changeSummaryPath(req.site_id, req.params.itemId)}/check-answers`, diff --git a/app/views/site/clinics/edit/check-answers.html b/app/views/site/clinics/edit/check-answers.html index 27170d2..4d3edd0 100644 --- a/app/views/site/clinics/edit/check-answers.html +++ b/app/views/site/clinics/edit/check-answers.html @@ -9,11 +9,40 @@ {% block content %}
+ {% macro servicesSummaryHtml(serviceIds) %} + {% set serviceNames = [] %} + {% for serviceId in serviceIds or [] %} + {% set serviceNames = serviceNames.concat([data.services[serviceId].name if data.services[serviceId] else serviceId]) %} + {% endfor %} + + {% if serviceNames | length > 0 %} +
    + {% for serviceName in serviceNames %} +
  • {{ serviceName }}
  • + {% endfor %} +
+ {% else %} + None selected + {% endif %} + {% endmacro %} + + {% macro servicesText(serviceIds) %} + {% set serviceNames = [] %} + {% for serviceId in serviceIds or [] %} + {% set serviceNames = serviceNames.concat([data.services[serviceId].name if data.services[serviceId] else serviceId]) %} + {% endfor %} + {{ serviceNames | join(', ') if (serviceNames | length) > 0 else 'None selected' }} + {% endmacro %} + + {% macro withPrevious(currentHtml, previousText) %} + {{ currentHtml | safe }}
Previously, {{ previousText or 'Not set' }} + {% endmacro %} +

{{ pageName or 'Check your answers' }}

{% set resolvedBookingActionText = 'No booking action selected' %} {% if bookingAction == 'cancel' %} - {% set resolvedBookingActionText = 'Cancel appointments' %} + {% set resolvedBookingActionText = 'Cancel ' ~ affectedCount ~ ' affected appointment' ~ ('s' if affectedCount != 1 else '') %} {% elif bookingAction == 'orphan' %} {% set resolvedBookingActionText = 'Keep booked appointments' %} {% endif %} @@ -21,11 +50,116 @@

{{ pageName or 'Check your answers' }}

{% set resolvedButtonText = buttonText or 'Save changes' %} {% set resolvedButtonClasses = '' %} {% if bookingAction == 'cancel' %} - {% set resolvedButtonText = 'Save changes and cancel bookings' %} + {% set resolvedButtonText = 'Save changes and cancel appointments' %} {% set resolvedButtonClasses = 'nhsuk-button--warning' %} {% endif %} - {% set allRows = rows %} + {% set allRows = [] %} + {% if rowFields and draft %} + {% for field in rowFields %} + {% set row = null %} + + {% if checkAnswersMode == 'clinic-edit' %} + {% if field == 'date' %} + {% set dateText = draft.startDate | formatDate('d MMM yyyy') %} + {% if isSeries %} + {% set dateText = (draft.startDate | formatDate('d MMM yyyy')) + ' to ' + (draft.endDate | formatDate('d MMM yyyy') ) %} + {% endif %} + {% set previousDateText = previous.startDate | formatDate('d MMM yyyy') if previous and previous.startDate else 'Not set' %} + {% if isSeries and previous and previous.endDate %} + {% set previousDateText = (previous.startDate | formatDate('d MMM yyyy')) + ' to ' + (previous.endDate | formatDate('d MMM yyyy')) %} + {% endif %} + {% set row = { + key: { text: 'Dates' if isSeries else 'Date' }, + value: { html: withPrevious(dateText, previousDateText) }, + actions: { items: [{ href: '/site/' + site_id + '/clinics/edit/' + sessionId + '/change/date', text: 'Change', visuallyHiddenText: ' dates' }] } + } %} + {% elif field == 'days' and isSeries %} + {% set previousDaysText = (previous.days or []) | join(', ') if previous else 'Not set' %} + {% set row = { + key: { text: 'Days' }, + value: { html: withPrevious((draft.days or []) | join(', '), previousDaysText) }, + actions: { items: [{ href: '/site/' + site_id + '/clinics/edit/' + sessionId + '/change/days', text: 'Change', visuallyHiddenText: ' days' }] } + } %} + {% elif field == 'time' %} + {% set previousTimeText = ((previous.from | nhsTime) + ' to ' + (previous.until | nhsTime)) if previous and previous.from and previous.until else 'Not set' %} + {% set row = { + key: { text: 'Time' }, + value: { html: withPrevious((draft.from | nhsTime) + ' to ' + (draft.until | nhsTime), previousTimeText) }, + actions: { items: [{ href: '/site/' + site_id + '/clinics/edit/' + sessionId + '/change/time', text: 'Change', visuallyHiddenText: ' time' }] } + } %} + {% elif field == 'capacity' %} + {% set row = { + key: { text: 'Vaccinators and capacity' }, + value: { html: withPrevious(draft.capacity, previous.capacity if previous else 'Not set') }, + actions: { items: [{ href: '/site/' + site_id + '/clinics/edit/' + sessionId + '/change/capacity', text: 'Change', visuallyHiddenText: ' vaccinators and capacity' }] } + } %} + {% elif field == 'duration' %} + {% set previousDurationText = (previous.duration + ' minutes') if previous and previous.duration else 'Not set' %} + {% set row = { + key: { text: 'Appointment length' }, + value: { html: withPrevious(draft.duration + ' minutes', previousDurationText) }, + actions: { items: [{ href: '/site/' + site_id + '/clinics/edit/' + sessionId + '/change/duration', text: 'Change', visuallyHiddenText: ' appointment length' }] } + } %} + {% elif field == 'services' %} + {% set currentServicesHtml = servicesSummaryHtml(draft.services) %} + {% set previousServicesText = servicesText(previous.services) if previous else 'Not set' %} + {% set row = { + key: { text: 'Services' }, + value: { html: withPrevious(currentServicesHtml, previousServicesText) }, + actions: { items: [{ href: '/site/' + site_id + '/clinics/edit/' + sessionId + '/change/services', text: 'Change', visuallyHiddenText: ' services' }] } + } %} + {% elif field == 'closures' and isSeries %} + {% set closuresText = 'None added' %} + {% if draft.closures and (draft.closures | length) > 0 %} + {% set closuresText = (draft.closures | length) + ' added' %} + {% endif %} + {% set previousClosuresText = (previous.closuresCount + ' added') if previous and previous.closuresCount and previous.closuresCount > 0 else 'None added' %} + {% set row = { + key: { text: 'Clinic closures' }, + value: { html: withPrevious(closuresText, previousClosuresText) }, + actions: { items: [{ href: '/site/' + site_id + '/clinics/edit/' + sessionId + '/change/closures', text: 'Change', visuallyHiddenText: ' clinic closures' }] } + } %} + {% endif %} + {% elif checkAnswersMode == 'session-change' %} + {% if field == 'name' %} + {% set row = { + key: { text: 'Name' }, + value: { html: withPrevious(draft.name or 'Not provided', previous.name if previous else 'Not set') }, + actions: { items: [{ href: '/site/' + site_id + '/change/session/' + sessionId + '/change/name', text: 'Change', visuallyHiddenText: ' name' }] } + } %} + {% elif field == 'time' %} + {% set previousTimeText = ((previous.from | nhsTime) + ' to ' + (previous.until | nhsTime)) if previous and previous.from and previous.until else 'Not set' %} + {% set row = { + key: { text: 'Time' }, + value: { html: withPrevious((draft.from | nhsTime) + ' to ' + (draft.until | nhsTime), previousTimeText) }, + actions: { items: [{ href: '/site/' + site_id + '/change/session/' + sessionId + '/change/time', text: 'Change', visuallyHiddenText: ' time' }] } + } %} + {% elif field == 'capacity' %} + {% set row = { + key: { text: 'Vaccinators' }, + value: { html: withPrevious(draft.capacity, previous.capacity if previous else 'Not set') }, + actions: { items: [{ href: '/site/' + site_id + '/change/session/' + sessionId + '/change/capacity', text: 'Change', visuallyHiddenText: ' vaccinators' }] } + } %} + {% elif field == 'services' %} + {% set currentServicesHtml = servicesSummaryHtml(draft.services) %} + {% set previousServicesText = servicesText(previous.services) if previous else 'Not set' %} + {% set row = { + key: { text: 'Services' }, + value: { html: withPrevious(currentServicesHtml, previousServicesText) }, + actions: { items: [{ href: '/site/' + site_id + '/change/session/' + sessionId + '/change/services', text: 'Change', visuallyHiddenText: ' services' }] } + } %} + {% endif %} + {% endif %} + + {% if row %} + {% set allRows = allRows.concat([row]) %} + {% endif %} + {% endfor %} + {% else %} + {% set allRows = rows or [] %} + {% endif %} + {% if affectedCount > 0 %} {% set allRows = allRows.concat([{ key: { text: 'Booked appointments' }, @@ -41,6 +175,7 @@

{{ pageName or 'Check your answers' }}

{% endif %} {{ summaryList({ rows: allRows }) }} + {{ button({ diff --git a/app/views/site/clinics/edit/success.html b/app/views/site/clinics/edit/success.html index b5ac4be..fe8478f 100644 --- a/app/views/site/clinics/edit/success.html +++ b/app/views/site/clinics/edit/success.html @@ -15,24 +15,48 @@

{{ cancelSummary.unnotifiedCount }} {{ 'person' if c

View the list of people who have not been notified

{% endif %} -

What would you like to do next?

+ {% if unaffectedChildClinics and unaffectedChildClinics.length %} + {% set unaffectedCount = unaffectedChildClinics.length %} +

{{ unaffectedCount }} {{ 'clinic has' if unaffectedCount == 1 else 'clinics have' }} not been updated

+
    + {% for clinicDate in unaffectedChildClinics %} +
  • {{ clinicDate }}
  • + {% endfor %} +
+ {% endif %} - {% for action in cancelSummary.nextActions %} -

{{ action.text }}

- {% endfor %} +

What would you like to do next?

+
    + {% for action in cancelSummary.nextActions %} +
  • {{ action.text }}
  • + {% endfor %} +
{% else %} {{ panel({ titleText: titleText or 'Clinic updated' }) }} - {% if primaryHref %} -

{{ primaryText or 'Continue' }}

- {% elif sessionId %} -

Make another change

- {% endif %} - {% if secondaryHref %} -

{{ secondaryText }}

- {% elif not primaryHref or sessionId %} -

Back to clinics

+ {% if unaffectedChildClinics and unaffectedChildClinics.length %} + {% set unaffectedCount = unaffectedChildClinics.length %} +

{{ unaffectedCount }} {{ 'clinic has' if unaffectedCount == 1 else 'clinics have' }} not been updated

+
    + {% for clinicDate in unaffectedChildClinics %} +
  • {{ clinicDate }}
  • + {% endfor %} +
{% endif %} +

What would you like to do next?

+ {% endif %}
diff --git a/app/views/site/clinics/edit/summary.html b/app/views/site/clinics/edit/summary.html index 9cb3d9d..78c1b95 100644 --- a/app/views/site/clinics/edit/summary.html +++ b/app/views/site/clinics/edit/summary.html @@ -29,25 +29,66 @@ {% set hasChange = true %}
Services
+ {% set baseServices = draft.services or [] %} + {% set effectiveServices = baseServices %} + {% set isReplaceMode = false %} + {% if childSession.services[0] is string %} - {% for serviceId in childSession.services %} - {{ data.services[serviceId].name if data.services[serviceId] else serviceId }}{% if not loop.last %}
{% endif %} - {% endfor %} + {% set effectiveServices = childSession.services %} + {% set isReplaceMode = true %} {% else %} {% for operation in childSession.services %} - {% set serviceName = data.services[operation.service].name if data.services[operation.service] else operation.service %} - {% if operation.operation == 'add' %} - Added {{ serviceName }} + {% set opService = operation.service %} + {% if operation.operation == 'replace' %} + {% if not isReplaceMode %} + {% set effectiveServices = [] %} + {% set isReplaceMode = true %} + {% endif %} + {% if opService and (opService not in effectiveServices) %} + {% set effectiveServices = effectiveServices.concat([opService]) %} + {% endif %} + {% elif operation.operation == 'add' %} + {% if opService and (opService not in effectiveServices) %} + {% set effectiveServices = effectiveServices.concat([opService]) %} + {% endif %} {% elif operation.operation == 'remove' %} - Removed {{ serviceName }} - {% elif operation.operation == 'replace' %} - Services replaced + {% set nextServices = [] %} + {% for existingService in effectiveServices %} + {% if existingService != opService %} + {% set nextServices = nextServices.concat([existingService]) %} + {% endif %} + {% endfor %} + {% set effectiveServices = nextServices %} + {% endif %} + {% endfor %} + {% endif %} + + {% set serviceLines = [] %} + + {% if isReplaceMode %} + {% for serviceId in effectiveServices %} + {% set serviceName = data.services[serviceId].name if data.services[serviceId] else serviceId %} + {% set serviceLines = serviceLines.concat([serviceName]) %} + {% endfor %} + {% else %} + {% for serviceId in baseServices %} + {% set serviceName = data.services[serviceId].name if data.services[serviceId] else serviceId %} + {% if serviceId in effectiveServices %} + {% set serviceLines = serviceLines.concat([serviceName]) %} {% else %} - Updated {{ serviceName }} + {% set serviceLines = serviceLines.concat(['' + serviceName + ' (removed)']) %} + {% endif %} + {% endfor %} + + {% for serviceId in effectiveServices %} + {% if serviceId not in baseServices %} + {% set serviceName = data.services[serviceId].name if data.services[serviceId] else serviceId %} + {% set serviceLines = serviceLines.concat([serviceName + ' (added)']) %} {% endif %} - {% if not loop.last %}
{% endif %} {% endfor %} {% endif %} + + {{ serviceLines | join('
') | safe }}
{% endif %} @@ -223,36 +264,48 @@

{{ pageName }}

{% set oneOffChangesItems = [] %} {% if draft.childSessions and draft.childSessions.length %} {% for childSession in draft.childSessions %} - {% set childDate = childSession.date | formatDate('d MMM yyyy') %} - {% set childInstanceId = '' %} - {% set daySessions = dailyAvailability[childSession.date].sessions if dailyAvailability and dailyAvailability[childSession.date] and dailyAvailability[childSession.date].sessions else [] %} - {% for daySession in daySessions %} - {% if daySession.recurringId == sessionId and not childInstanceId %} - {% set childInstanceId = daySession.id %} - {% endif %} - {% endfor %} + {% set includeOneOffChange = true %} + {% if not childSession.date or childSession.date < draft.startDate or childSession.date > draft.endDate %} + {% set includeOneOffChange = false %} + {% endif %} - {% set childChangeHref = editBasePath %} - {% if childInstanceId %} - {% set backHref = editBasePath %} - {% set childChangeHref = '/site/' + site_id + '/change/session/' + childInstanceId + '?back=' + (backHref | urlencode) %} + {% if includeOneOffChange and draft.closures and draft.closures.length %} + {% for closure in draft.closures %} + {% if closure.startDate and closure.endDate and childSession.date >= closure.startDate and childSession.date <= closure.endDate %} + {% set includeOneOffChange = false %} + {% endif %} + {% endfor %} {% endif %} - {% set oneOffChangesItems = oneOffChangesItems.concat([ - { - key: childDate, - html: oneOffChangesSummary(childSession), - actions: { - items: [ - { - href: childChangeHref, - text: 'Change', - visuallyHiddenText: ' one-off changes for ' + childDate + {% if includeOneOffChange %} + {% set childDate = childSession.date | formatDate('d MMM yyyy') %} + {% set childInstanceId = '' %} + {% set daySessions = dailyAvailability[childSession.date].sessions if dailyAvailability and dailyAvailability[childSession.date] and dailyAvailability[childSession.date].sessions else [] %} + {% for daySession in daySessions %} + {% if daySession.recurringId == sessionId and not childInstanceId %} + {% set childInstanceId = daySession.id %} + {% endif %} + {% endfor %} + + {% if childInstanceId %} + {% set backHref = editBasePath %} + {% set oneOffChangesItems = oneOffChangesItems.concat([ + { + key: childDate, + html: oneOffChangesSummary(childSession), + actions: { + items: [ + { + href: '/site/' + site_id + '/change/session/' + childInstanceId + '?back=' + (backHref | urlencode), + text: 'Change', + visuallyHiddenText: ' one-off changes for ' + childDate + } + ] } - ] - } - } - ]) %} + } + ]) %} + {% endif %} + {% endif %} {% endfor %} {% endif %} diff --git a/app/views/site/clinics/series/success.html b/app/views/site/clinics/series/success.html index 40e87fb..8b4e8df 100644 --- a/app/views/site/clinics/series/success.html +++ b/app/views/site/clinics/series/success.html @@ -9,8 +9,7 @@ titleText: pageName }) }} -

What happens next

-

You can review and change this clinic from the appointments screens.

+

What would you like to do next?

{{ insetText({ text: 'People will now be able to book appointments in this clinic.' diff --git a/app/views/site/clinics/single/success.html b/app/views/site/clinics/single/success.html index 40e87fb..8b4e8df 100644 --- a/app/views/site/clinics/single/success.html +++ b/app/views/site/clinics/single/success.html @@ -9,8 +9,7 @@ titleText: pageName }) }} -

What happens next

-

You can review and change this clinic from the appointments screens.

+

What would you like to do next?

{{ insetText({ text: 'People will now be able to book appointments in this clinic.'