From 400b85935a0189a395f72846d969e9d1207de784 Mon Sep 17 00:00:00 2001 From: roobottom Date: Mon, 16 Mar 2026 22:53:02 +0000 Subject: [PATCH 1/3] Implement D-05 single-session subflow saves --- app/routes/sessions.js | 84 ++++++++++++++++++++---- app/routes/shared/session-edit.js | 1 + app/views/sites/session-edit-check.html | 87 +++++++++++++++++++++++++ app/views/sites/session-options.html | 4 +- 4 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 app/views/sites/session-edit-check.html diff --git a/app/routes/sessions.js b/app/routes/sessions.js index 8024020..a6d14d7 100644 --- a/app/routes/sessions.js +++ b/app/routes/sessions.js @@ -26,6 +26,40 @@ function sessions (router, shared) { return `/sites/${siteId}/session/${availId}/${date}/breaks` } + function getSessionEditBaseHref (siteId, availId, date) { + return `/sites/${siteId}/session/${availId}/${date}/edit` + } + + function getSessionEditCheckHref (siteId, availId, date) { + return `${getSessionEditBaseHref(siteId, availId, date)}/check` + } + + function getSessionEditPathHref (siteId, availId, date, editPath) { + const base = getSessionEditBaseHref(siteId, availId, date) + if (editPath === 'booking-length') return `${base}/booking-length` + if (editPath === 'services') return `${base}/services` + return `${base}/times` + } + + function renderSessionEditCheckPage (req, res, avail, draft) { + const serviceNames = (draft.services || []) + .map(id => (req.data.services[id] ? req.data.services[id].name : id)) + res.render('sites/session-edit-check', { + site: req.site, + sessionLabel: avail.label, + date: req.params.date, + heading: draft.editPath === 'booking-length' + ? 'Check booking length changes' + : (draft.editPath === 'services' ? 'Check service changes' : 'Check session time changes'), + draft, + serviceNames, + formAction: getSessionEditCheckHref(req.site.id, avail.id, req.params.date), + backHref: getSessionEditPathHref(req.site.id, avail.id, req.params.date, draft.editPath), + changeHref: getSessionEditPathHref(req.site.id, avail.id, req.params.date, draft.editPath), + affectedCount: (draft.affectedBookingIds || []).length + }) + } + function getSessionBreakBackHref (siteId, availId, date, draft) { const base = getSessionBreaksBaseHref(siteId, availId, date) if (draft.breakAction === 'change' && Number.isInteger(draft.breakIndex)) return `${base}/${draft.breakIndex}/change` @@ -110,16 +144,7 @@ function sessions (router, shared) { }) router.post('/sites/:siteId/session/:availId/:date', (req, res) => { - const avail = req.site.availability.find(a => a.id === req.params.availId) - if (!avail) return res.status(404).send('Availability not found') - const draft = ensureSessionEditDraft(req, avail, req.params.date) - - if ((draft.affectedBookingIds || []).length > 0) { - return res.redirect(`${getSessionEditInfoHref(req.site.id, avail.id, req.params.date)}/warning`) - } - - applySessionEditDraft(req, avail, draft) - res.redirect(`${getSessionEditInfoHref(req.site.id, avail.id, req.params.date)}/success`) + res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) }) router.get('/sites/:siteId/session/:availId/:date/remove', (req, res) => { @@ -469,6 +494,7 @@ function sessions (router, shared) { const draft = req.session.data.sessionEditDraft if (!draft) return res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) const base = `/sites/${req.site.id}/session/${req.params.availId}/${req.params.date}/edit` + draft.editPath = 'times' res.render('sites/availability-shared-times', { site: req.site, @@ -501,14 +527,16 @@ function sessions (router, shared) { } draft.timeBlocks = parsedBlocks + draft.editPath = 'times' updateSessionEditDraftImpacts(req.site, req.params.availId, req.params.date, draft) - res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) + res.redirect(getSessionEditCheckHref(req.site.id, req.params.availId, req.params.date)) }) router.get('/sites/:siteId/session/:availId/:date/edit/booking-length', (req, res) => { const draft = req.session.data.sessionEditDraft if (!draft) return res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) const base = `/sites/${req.site.id}/session/${req.params.availId}/${req.params.date}/edit` + draft.editPath = 'booking-length' res.render('sites/availability-shared-booking-length', { site: req.site, @@ -538,14 +566,16 @@ function sessions (router, shared) { } draft.slotLength = value + draft.editPath = 'booking-length' updateSessionEditDraftImpacts(req.site, req.params.availId, req.params.date, draft) - res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) + res.redirect(getSessionEditCheckHref(req.site.id, req.params.availId, req.params.date)) }) router.get('/sites/:siteId/session/:availId/:date/edit/services', (req, res) => { const draft = req.session.data.sessionEditDraft if (!draft) return res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) const base = `/sites/${req.site.id}/session/${req.params.availId}/${req.params.date}/edit` + draft.editPath = 'services' res.render('sites/availability-shared-services', { site: req.site, @@ -579,8 +609,32 @@ function sessions (router, shared) { } draft.services = services + draft.editPath = 'services' updateSessionEditDraftImpacts(req.site, req.params.availId, req.params.date, draft) - res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) + res.redirect(getSessionEditCheckHref(req.site.id, req.params.availId, req.params.date)) + }) + + router.get('/sites/:siteId/session/:availId/:date/edit/check', (req, res) => { + const avail = req.site.availability.find(a => a.id === req.params.availId) + const draft = req.session.data.sessionEditDraft + if (!avail) return res.status(404).send('Availability not found') + if (!draft || !draft.editPath) return res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) + + renderSessionEditCheckPage(req, res, avail, draft) + }) + + router.post('/sites/:siteId/session/:availId/:date/edit/check', (req, res) => { + const avail = req.site.availability.find(a => a.id === req.params.availId) + const draft = req.session.data.sessionEditDraft + if (!avail) return res.status(404).send('Availability not found') + if (!draft || !draft.editPath) return res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) + + if ((draft.affectedBookingIds || []).length > 0) { + return res.redirect(`${getSessionEditInfoHref(req.site.id, avail.id, req.params.date)}/warning`) + } + + applySessionEditDraft(req, avail, draft) + res.redirect(`${getSessionEditInfoHref(req.site.id, avail.id, req.params.date)}/success`) }) router.get('/sites/:siteId/session/:availId/:date/edit/warning', (req, res) => { @@ -608,7 +662,9 @@ function sessions (router, shared) { affectedCount: affected.length, affectedBookingRows, formAction: `${base}/warning`, - backHref: getSessionEditSummaryHref(site.id, req.params.availId, date) + backHref: draft.editPath + ? getSessionEditCheckHref(site.id, req.params.availId, date) + : getSessionEditSummaryHref(site.id, req.params.availId, date) }) }) diff --git a/app/routes/shared/session-edit.js b/app/routes/shared/session-edit.js index 5d16398..b929a91 100644 --- a/app/routes/shared/session-edit.js +++ b/app/routes/shared/session-edit.js @@ -123,6 +123,7 @@ function buildSessionEditDraft (avail, date) { originalBreakTimes: avail.breakTimes || [], affectedBookingIds: [], unaffectedBookingIds: [], + editPath: null, changeDescription: 'session times', bookingsChoice: 'keep' } diff --git a/app/views/sites/session-edit-check.html b/app/views/sites/session-edit-check.html new file mode 100644 index 0000000..ad67922 --- /dev/null +++ b/app/views/sites/session-edit-check.html @@ -0,0 +1,87 @@ +{% extends 'layout.html' %} + +{% set pageName = heading + " — " + site.name %} + +{% block beforeContent %} + {{ backLink({ href: backHref }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ heading }}

+ +
+
+

Session details

+
+
+ {{ summaryList({ rows: [ + { + key: { text: "Session" }, + value: { text: sessionLabel } + }, + { + key: { text: "Date" }, + value: { text: date | formatDateLong } + } + ] }) }} +
+
+ +
+
+

Change details

+ +
+
+ {% if draft.editPath === 'booking-length' %} + {{ summaryList({ rows: [ + { + key: { text: "Booking length" }, + value: { text: draft.slotLength + " minutes" } + } + ] }) }} + {% elif draft.editPath === 'services' %} + {{ summaryList({ rows: [ + { + key: { text: "Services" }, + value: { text: serviceNames | join(", ") } + } + ] }) }} + {% else %} + {% set blocksList %} +
    + {% for blk in draft.timeBlocks %} +
  • {{ blk.start | formatTime }} to {{ blk.end | formatTime }}
  • + {% endfor %} +
+ {% endset %} + {{ summaryList({ rows: [ + { + key: { text: "Time periods" }, + value: { html: blocksList } + } + ] }) }} + {% endif %} +
+
+ + {% if affectedCount > 0 %} + {% call insetText({}) %} +

{{ affectedCount }} {{ "booking is" if affectedCount === 1 else "bookings are" }} affected by this change.

+ {% endcall %} + {% endif %} + +
+ {{ button({ text: "Save changes" }) }} +
+ +
+
+{% endblock %} diff --git a/app/views/sites/session-options.html b/app/views/sites/session-options.html index ebbad91..f8e430e 100644 --- a/app/views/sites/session-options.html +++ b/app/views/sites/session-options.html @@ -94,9 +94,7 @@

Services

-
- {{ button({ text: "Save changes" }) }} -
+

Choose one thing to change. Each change is saved at the end of its own journey.

From 5beb705e4d930a776fd7a33f5c29236fec32d2d3 Mon Sep 17 00:00:00 2001 From: roobottom Date: Mon, 16 Mar 2026 23:04:02 +0000 Subject: [PATCH 2/3] Fix D-05 edit flow order --- app/routes/sessions.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/routes/sessions.js b/app/routes/sessions.js index a6d14d7..97a10ec 100644 --- a/app/routes/sessions.js +++ b/app/routes/sessions.js @@ -529,6 +529,10 @@ function sessions (router, shared) { draft.timeBlocks = parsedBlocks draft.editPath = 'times' updateSessionEditDraftImpacts(req.site, req.params.availId, req.params.date, draft) + if ((draft.affectedBookingIds || []).length > 0) { + return res.redirect(`${getSessionEditInfoHref(req.site.id, req.params.availId, req.params.date)}/warning`) + } + res.redirect(getSessionEditCheckHref(req.site.id, req.params.availId, req.params.date)) }) @@ -568,6 +572,10 @@ function sessions (router, shared) { draft.slotLength = value draft.editPath = 'booking-length' updateSessionEditDraftImpacts(req.site, req.params.availId, req.params.date, draft) + if ((draft.affectedBookingIds || []).length > 0) { + return res.redirect(`${getSessionEditInfoHref(req.site.id, req.params.availId, req.params.date)}/warning`) + } + res.redirect(getSessionEditCheckHref(req.site.id, req.params.availId, req.params.date)) }) @@ -611,6 +619,10 @@ function sessions (router, shared) { draft.services = services draft.editPath = 'services' updateSessionEditDraftImpacts(req.site, req.params.availId, req.params.date, draft) + if ((draft.affectedBookingIds || []).length > 0) { + return res.redirect(`${getSessionEditInfoHref(req.site.id, req.params.availId, req.params.date)}/warning`) + } + res.redirect(getSessionEditCheckHref(req.site.id, req.params.availId, req.params.date)) }) @@ -629,10 +641,6 @@ function sessions (router, shared) { if (!avail) return res.status(404).send('Availability not found') if (!draft || !draft.editPath) return res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) - if ((draft.affectedBookingIds || []).length > 0) { - return res.redirect(`${getSessionEditInfoHref(req.site.id, avail.id, req.params.date)}/warning`) - } - applySessionEditDraft(req, avail, draft) res.redirect(`${getSessionEditInfoHref(req.site.id, avail.id, req.params.date)}/success`) }) @@ -663,7 +671,7 @@ function sessions (router, shared) { affectedBookingRows, formAction: `${base}/warning`, backHref: draft.editPath - ? getSessionEditCheckHref(site.id, req.params.availId, date) + ? getSessionEditPathHref(site.id, req.params.availId, date, draft.editPath) : getSessionEditSummaryHref(site.id, req.params.availId, date) }) }) @@ -675,8 +683,7 @@ function sessions (router, shared) { if (!draft) return res.redirect(getSessionEditSummaryHref(req.site.id, req.params.availId, req.params.date)) draft.bookingsChoice = req.body.bookingsChoice || 'keep' - applySessionEditDraft(req, avail, draft) - res.redirect(`${getSessionEditInfoHref(req.site.id, avail.id, req.params.date)}/success`) + res.redirect(getSessionEditCheckHref(req.site.id, avail.id, req.params.date)) }) router.get('/sites/:siteId/session/:availId/:date/edit/success', (req, res) => { From 4782caf621d8119df2e288e276b9b5d9c9131902 Mon Sep 17 00:00:00 2001 From: roobottom Date: Mon, 16 Mar 2026 23:20:37 +0000 Subject: [PATCH 3/3] Refine D-05 check answers flow --- app/routes/sessions.js | 11 +- app/views/sites/session-edit-check.html | 155 ++++++++++++++---------- app/views/sites/session-options.html | 3 - 3 files changed, 97 insertions(+), 72 deletions(-) diff --git a/app/routes/sessions.js b/app/routes/sessions.js index 97a10ec..c872c7f 100644 --- a/app/routes/sessions.js +++ b/app/routes/sessions.js @@ -44,19 +44,22 @@ function sessions (router, shared) { function renderSessionEditCheckPage (req, res, avail, draft) { const serviceNames = (draft.services || []) .map(id => (req.data.services[id] ? req.data.services[id].name : id)) + const editLabel = draft.editPath === 'booking-length' + ? 'booking length' + : (draft.editPath === 'services' ? 'services' : 'session times') + res.render('sites/session-edit-check', { site: req.site, sessionLabel: avail.label, date: req.params.date, - heading: draft.editPath === 'booking-length' - ? 'Check booking length changes' - : (draft.editPath === 'services' ? 'Check service changes' : 'Check session time changes'), + editLabel, draft, serviceNames, formAction: getSessionEditCheckHref(req.site.id, avail.id, req.params.date), backHref: getSessionEditPathHref(req.site.id, avail.id, req.params.date, draft.editPath), changeHref: getSessionEditPathHref(req.site.id, avail.id, req.params.date, draft.editPath), - affectedCount: (draft.affectedBookingIds || []).length + affectedCount: (draft.affectedBookingIds || []).length, + bookingsHref: `${getSessionEditInfoHref(req.site.id, avail.id, req.params.date)}/warning` }) } diff --git a/app/views/sites/session-edit-check.html b/app/views/sites/session-edit-check.html index ad67922..4244303 100644 --- a/app/views/sites/session-edit-check.html +++ b/app/views/sites/session-edit-check.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} -{% set pageName = heading + " — " + site.name %} +{% set pageName = "Check your answers — " + site.name %} {% block beforeContent %} {{ backLink({ href: backHref }) }} @@ -10,77 +10,102 @@
-

{{ heading }}

+

Check your answers

-
-
-

Session details

-
-
- {{ summaryList({ rows: [ - { - key: { text: "Session" }, - value: { text: sessionLabel } - }, - { - key: { text: "Date" }, - value: { text: date | formatDateLong } - } - ] }) }} -
-
+

Review the changes to {{ editLabel }} before saving.

-
-
-

Change details

-
    -
  • - Change change details -
  • + {% set summaryRows = [ + { + key: { text: "Session" }, + value: { text: sessionLabel } + }, + { + key: { text: "Date" }, + value: { text: date | formatDateLong } + } + ] %} + + {% if draft.editPath === 'booking-length' %} + {% set summaryRows = (summaryRows.push({ + key: { text: "Booking length" }, + value: { text: draft.slotLength + " minutes" }, + actions: { + items: [{ + href: changeHref, + text: "Change", + visuallyHiddenText: "booking length" + }] + } + }), summaryRows) %} + {% elif draft.editPath === 'services' %} + {% set summaryRows = (summaryRows.push({ + key: { text: "Services" }, + value: { text: serviceNames | join(", ") }, + actions: { + items: [{ + href: changeHref, + text: "Change", + visuallyHiddenText: "services" + }] + } + }), summaryRows) %} + {% else %} + {% set blocksList %} +
      + {% for blk in draft.timeBlocks %} +
    • {{ blk.start | formatTime }} to {{ blk.end | formatTime }}
    • + {% endfor %}
    -
-
- {% if draft.editPath === 'booking-length' %} - {{ summaryList({ rows: [ - { - key: { text: "Booking length" }, - value: { text: draft.slotLength + " minutes" } - } - ] }) }} - {% elif draft.editPath === 'services' %} - {{ summaryList({ rows: [ - { - key: { text: "Services" }, - value: { text: serviceNames | join(", ") } - } - ] }) }} - {% else %} - {% set blocksList %} -
    - {% for blk in draft.timeBlocks %} -
  • {{ blk.start | formatTime }} to {{ blk.end | formatTime }}
  • - {% endfor %} -
- {% endset %} - {{ summaryList({ rows: [ - { - key: { text: "Time periods" }, - value: { html: blocksList } - } - ] }) }} - {% endif %} -
-
+ {% endset %} + {% set summaryRows = (summaryRows.push({ + key: { text: "Time periods" }, + value: { html: blocksList }, + actions: { + items: [{ + href: changeHref, + text: "Change", + visuallyHiddenText: "session times" + }] + } + }), summaryRows) %} + {% endif %} {% if affectedCount > 0 %} - {% call insetText({}) %} -

{{ affectedCount }} {{ "booking is" if affectedCount === 1 else "bookings are" }} affected by this change.

- {% endcall %} + {% set summaryRows = (summaryRows.push({ + key: { text: "Bookings affected" }, + value: { text: affectedCount } + }), summaryRows) %} + {% set summaryRows = (summaryRows.push({ + key: { text: "What do you want to do with bookings" }, + value: { text: "Keep bookings" if draft.bookingsChoice === "keep" else ("Cancel " + affectedCount + " " + ("booking" if affectedCount === 1 else "bookings")) }, + actions: { + items: [{ + href: bookingsHref, + text: "Change", + visuallyHiddenText: "what to do with bookings" + }] + } + }), summaryRows) %} {% endif %} -
- {{ button({ text: "Save changes" }) }} -
+ {{ summaryList({ rows: summaryRows }) }} + + {% if affectedCount > 0 and draft.bookingsChoice === "cancel" %} + {% call warningCallout({ heading: "You are about to remove " + affectedCount + " " + ("booking" if affectedCount === 1 else "bookings") }) %} +

This cannot be undone.

+ {% endcall %} + +
+ {{ button({ + text: "Change session and remove " + affectedCount + " " + ("booking" if affectedCount === 1 else "bookings"), + classes: "nhsuk-button--warning" + }) }} +
+ {% else %} +
+ {{ button({ text: "Change session" }) }} +
+ {% endif %}
diff --git a/app/views/sites/session-options.html b/app/views/sites/session-options.html index f8e430e..3d50489 100644 --- a/app/views/sites/session-options.html +++ b/app/views/sites/session-options.html @@ -93,9 +93,6 @@

Services

] }) }} - -

Choose one thing to change. Each change is saved at the end of its own journey.

- {% endblock %}