Skip to content

Commit bc50ab8

Browse files
Workflow part 2 (#140)
* Commit all the things * Cleanup * Set status correctly from index * Fix check-in links * Add special appointment details * Fix data persisting after starting event * Persist return url
1 parent ab86318 commit bc50ab8

35 files changed

+688
-606
lines changed

app/assets/javascript/main.js

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', () => {
1313
const link = e.currentTarget
1414
const clinicId = link.dataset.clinicId
1515
const eventId = link.dataset.eventId
16-
const showAppointmentLink = link.dataset.showAppointmentLink === 'true'
16+
const statusTagId = link.dataset.statusTagId
1717

1818
try {
1919
const response = await fetch(
@@ -30,27 +30,38 @@ document.addEventListener('DOMContentLoaded', () => {
3030
throw new Error('Failed to check in participant')
3131
}
3232

33-
// Find the containing element by data attribute
34-
const container = document.querySelector(`[data-event-status-container="${eventId}"]`)
35-
if (container) {
36-
let html = `
37-
<strong class="nhsuk-tag">
38-
Checked in
39-
</strong>
40-
`
33+
// Update the status tag if we have an ID
34+
if (statusTagId) {
35+
const statusTag = document.getElementById(statusTagId)
36+
if (statusTag) {
37+
// Update the existing tag's text and classes
38+
statusTag.textContent = 'Checked in'
39+
statusTag.className = 'nhsuk-tag app-nowrap'
40+
}
41+
}
4142

42-
// Todo: this link should include the participant's name in hidden text
43+
// Remove the check-in link
44+
// Check if this is a modal button or a direct link
45+
const isModalButton = link.closest('.app-modal')
4346

44-
// Add appointment link if enabled
45-
if (showAppointmentLink) {
46-
html += `
47-
<p class="nhsuk-u-margin-top-2 nhsuk-u-margin-bottom-2">
48-
<a href="/clinics/${clinicId}/events/${eventId}/start?event[workflowStatus][appointment]=started">Start appointment</a>
49-
</p>
50-
`
51-
}
47+
if (isModalButton) {
48+
// For modal buttons, find the original check-in link on the main page
49+
// Look for a link that opens the modal for this specific event
50+
const modalId = `check-in-modal-${eventId}`
51+
const originalCheckInLink = document.querySelector(`a[onclick*="openModal('${modalId}')"]`)
5252

53-
container.innerHTML = html
53+
if (originalCheckInLink) {
54+
const checkInParagraph = originalCheckInLink.closest('p')
55+
if (checkInParagraph) {
56+
checkInParagraph.remove()
57+
}
58+
}
59+
} else {
60+
// For direct links, remove the paragraph containing the link
61+
const checkInParagraph = link.closest('p')
62+
if (checkInParagraph) {
63+
checkInParagraph.remove()
64+
}
5465
}
5566

5667
// Close any open modal (for modal-based check-ins)

app/filters/tags.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const toTag = (status, options = {}) => {
3030
].filter(Boolean).join(' ')
3131

3232
// Generate tag HTML
33-
return `<strong class="${classes}">${text}</strong>`
33+
const idAttr = options.id ? ` id=\"${options.id}\"` : ''
34+
return `<strong${idAttr} class="${classes}">${text}</strong>`
3435
}
3536

3637
module.exports = {

app/lib/utils/event-data.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,14 @@ const updateEventStatus = (data, eventId, newStatus) => {
5353
const eventIndex = data.events.findIndex(e => e.id === eventId)
5454
if (eventIndex === -1) return null
5555

56-
const event = data.events[eventIndex]
56+
// Use temp event if it exists and matches, otherwise use the array event
57+
const baseEvent = (data.event && data.event.id === eventId) ? data.event : data.events[eventIndex]
5758

5859
const updatedEvent = {
59-
...event,
60+
...baseEvent,
6061
status: newStatus,
6162
statusHistory: [
62-
...event.statusHistory,
63+
...baseEvent.statusHistory,
6364
{
6465
status: newStatus,
6566
timestamp: new Date().toISOString(),
@@ -71,8 +72,10 @@ const updateEventStatus = (data, eventId, newStatus) => {
7172
data.events[eventIndex] = updatedEvent
7273

7374
// Also update temp event data if it exists and matches this event
75+
// Only update the status-related fields to preserve other temp changes
7476
if (data.event && data.event.id === eventId) {
75-
data.event = { ...updatedEvent }
77+
data.event.status = newStatus
78+
data.event.statusHistory = updatedEvent.statusHistory
7679
}
7780

7881
return updatedEvent
@@ -90,10 +93,11 @@ const updateEventData = (data, eventId, updates) => {
9093
const eventIndex = data.events.findIndex(e => e.id === eventId)
9194
if (eventIndex === -1) return null
9295

93-
const event = data.events[eventIndex]
96+
// Use temp event if it exists and matches, otherwise use the array event
97+
const baseEvent = (data.event && data.event.id === eventId) ? data.event : data.events[eventIndex]
9498

9599
const updatedEvent = {
96-
...event,
100+
...baseEvent,
97101
...updates
98102
}
99103

app/lib/utils/status.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const dayjs = require('dayjs')
77
* @type {Object}
88
*/
99
const STATUS_GROUPS = {
10+
not_started: ['event_scheduled', 'event_checked_in'],
1011
completed: ['event_complete', 'event_partially_screened'],
1112
final: ['event_complete', 'event_partially_screened', 'event_did_not_attend', 'event_attended_not_screened', 'event_cancelled'],
1213
active: ['event_scheduled', 'event_checked_in', 'event_in_progress'],
@@ -35,6 +36,18 @@ const getStatus = (input) => {
3536
return input.status || null
3637
}
3738

39+
/**
40+
* Check if a status represents a not started event
41+
* @param {string|Object} input - Status string or event object
42+
* @returns {boolean} Whether the status is not started
43+
*/
44+
const hasNotStarted = (input) => {
45+
const status = getStatus(input)
46+
if (!status) return false
47+
return isStatusInGroup(status, 'not_started')
48+
}
49+
50+
3851
/**
3952
* Check if a status represents a completed event
4053
* @param {string|Object} input - Status string or event object
@@ -248,6 +261,7 @@ const isSpecialAppointment = (event) => {
248261
}
249262

250263
module.exports = {
264+
hasNotStarted,
251265
isCompleted,
252266
isInProgress,
253267
isFinal,

app/routes/events.js

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,12 @@ module.exports = router => {
127127
next()
128128
})
129129

130-
// Main route in to starting an event - used to clear any temp data
131-
// Possibly not needed as if the ID doesn't match our use route would reset this - but this could
132-
// be used to reset a patient back to defaults
130+
// Main route in to starting an event - used to clear any temp data, set status to in progress and store the user id of the mammographer doing the appointment
133131
router.get('/clinics/:clinicId/events/:eventId/start', (req, res) => {
134132
const data = req.session.data
135133
const event = getEvent(data, req.params.eventId)
136134
const currentUser = data.currentUser
135+
const returnTo = req.query.returnTo // Used by /index so we can 'start' an appointment but then go to a different page.
137136

138137
if (event?.status !== 'event_in_progress') {
139138
// Update status
@@ -148,22 +147,77 @@ module.exports = router => {
148147
})
149148
}
150149

151-
// Explicitly delete the temp event data just in case
152-
// On next request this will be recreated from the event array
153-
delete data.event
154-
console.log('Cleared temp event data')
150+
// Parse and apply workflow status from query parameters
151+
// This lets links in index.njk pre-complete certain sections
152+
// Look for parameters like event[workflowStatus][section]=completed
153+
if (req.query.event && req.query.event.workflowStatus) {
154+
const workflowUpdates = req.query.event.workflowStatus
155+
console.log('Applying workflow status updates:', workflowUpdates)
156+
157+
updateEventData(data, req.params.eventId, {
158+
workflowStatus: workflowUpdates
159+
})
160+
}
161+
162+
// Determine redirect destination
163+
// This lets us deep link in to the flow whilst still going through this setup route
164+
const defaultDestination = `/clinics/${req.params.clinicId}/events/${req.params.eventId}/identity`
165+
const finalDestination = returnTo
166+
? `/clinics/${req.params.clinicId}/events/${req.params.eventId}/${returnTo}`
167+
: defaultDestination
155168

156-
res.redirect(`/clinics/${req.params.clinicId}/events/${req.params.eventId}/identity`)
169+
res.redirect(finalDestination)
170+
})
171+
172+
// Leave appointment - revert status from in_progress back to checked_in
173+
router.get('/clinics/:clinicId/events/:eventId/leave', (req, res) => {
174+
const { clinicId, eventId } = req.params
175+
const data = req.session.data
176+
const event = getEvent(data, eventId)
177+
178+
// Only allow leaving if the event is currently in progress
179+
if (event?.status === 'event_in_progress') {
180+
// Save any temporary changes before leaving
181+
saveTempEventToEvent(data)
182+
saveTempParticipantToParticipant(data)
183+
184+
// Revert status back to checked in
185+
updateEventStatus(data, eventId, 'event_checked_in')
186+
187+
// Clear session details
188+
updateEventData(data, eventId, {
189+
sessionDetails: {
190+
startedAt: null,
191+
startedBy: null
192+
}
193+
})
194+
195+
// Clear temporary session data (now safe since we've saved changes)
196+
delete data.event
197+
delete data.participant
198+
199+
console.log('Left appointment - saved temp data, reverted status to checked_in, and cleared temp data')
200+
201+
// req.flash('info', 'You have left the appointment. The participant remains checked in.')
202+
}
203+
204+
// Use referrer chain for redirect, fallback to clinic view
205+
const returnUrl = getReturnUrl(`/clinics/${clinicId}`, req.query.referrerChain)
206+
res.redirect(returnUrl)
157207
})
158208

159209
// Event within clinic context
160210
router.get('/clinics/:clinicId/events/:eventId', (req, res) => {
161-
res.render('events/show', {
162-
})
211+
const { clinicId, eventId } = req.params
212+
// res.render('events/show', {
213+
// })
214+
res.redirect(`/clinics/${clinicId}/events/${eventId}/appointment`)
163215
})
164216

165217

166218

219+
220+
167221
router.post('/clinics/:clinicId/events/:eventId/personal-details/ethnicity-answer', (req, res) => {
168222
const { clinicId, eventId } = req.params
169223
const data = req.session.data
@@ -269,6 +323,8 @@ module.exports = router => {
269323
const data = req.session.data
270324
const previousMammogram = data.event?.previousMammogramTemp
271325
const action = req.body.action
326+
const referrerChain = req.query.referrerChain
327+
const scrollTo = req.query.scrollTo
272328

273329
const mammogramAddedMessage = 'Previous mammogram added'
274330

@@ -297,7 +353,11 @@ module.exports = router => {
297353
req.flash('success', mammogramAddedMessage)
298354

299355
delete data.event?.previousMammogramTemp
300-
return res.redirect(`/clinics/${clinicId}/events/${eventId}`)
356+
// return res.redirect(`/clinics/${clinicId}/events/${eventId}`)
357+
358+
const returnUrl = getReturnUrl(`/clinics/${clinicId}/events/${eventId}`, referrerChain, scrollTo)
359+
360+
res.redirect(returnUrl)
301361
}
302362

303363
// Handle the direct cancel action from appointment-should-not-proceed.html
@@ -339,7 +399,7 @@ module.exports = router => {
339399

340400
// If recent mammogram detected and not already coming from warning page
341401
if (isRecentMammogram && action !== 'continue') {
342-
return res.redirect(`/clinics/${clinicId}/events/${eventId}/previous-mammograms/appointment-should-not-proceed`)
402+
return res.redirect(urlWithReferrer(`/clinics/${clinicId}/events/${eventId}/previous-mammograms/appointment-should-not-proceed`, referrerChain, scrollTo))
343403
}
344404

345405
// Normal flow - save the mammogram
@@ -366,7 +426,9 @@ module.exports = router => {
366426

367427
req.flash('success', mammogramAddedMessage)
368428

369-
res.redirect(`/clinics/${clinicId}/events/${eventId}`)
429+
const returnUrl = getReturnUrl(`/clinics/${clinicId}/events/${eventId}`, referrerChain, scrollTo)
430+
res.redirect(returnUrl)
431+
370432
})
371433

372434
// Helper function to check if mammogram was taken within the last 6 months
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{# app/views/_includes/check-in/macro.njk #}
2+
3+
{% macro appCheckIn(params) %}
4+
{%- include './template.njk' -%}
5+
{% endmacro %}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
2+
{# app/views/_components/check-in/template.njk #}
3+
4+
{% from '_components/modal/macro.njk' import appModal %}
5+
{% from '_components/summary-list/macro.njk' import appSummaryList as summaryList %}
6+
{% from '_components/summary-list/macro.njk' import appSummaryListRow %}
7+
8+
{# Build check-in href #}
9+
{% set checkInHref -%}
10+
/clinics/{{ params.clinicId }}/check-in/{{ params.event.id }}
11+
{%- endset %}
12+
13+
{# Check if identity confirmation is enabled #}
14+
{% set confirmIdentityEnabled = params.confirmIdentityOnCheckIn %}
15+
{% set statusTagId = params.statusTagId %}
16+
17+
{% if confirmIdentityEnabled %}
18+
{% set modalId = "check-in-modal-" + params.event.id %}
19+
{% set statusTagId = params.statusTagId %}
20+
{% include "_includes/confirm-identity-modal.njk" %}
21+
{% endif %}
22+
23+
{# Only show check-in for scheduled events #}
24+
{% if params.event.status === 'event_scheduled' %}
25+
{% set checkInText %}
26+
Check in{{ (" " + (params.participant | getShortName)) | asVisuallyHiddenText if params.participant }}
27+
{% endset %}
28+
29+
<p class="nhsuk-u-margin-bottom-2">
30+
{% if confirmIdentityEnabled %}
31+
{# Modal-based check-in #}
32+
<a href=""
33+
class="nhsuk-link nhsuk-link--no-visited-state"
34+
data-status-tag-id="{{ statusTagId }}"
35+
onclick="openModal('{{ modalId }}'); return false;">
36+
{{ checkInText | safe }}
37+
</a>
38+
{% else %}
39+
{# Direct check-in without identity confirmation #}
40+
<a href="{{ checkInHref }}"
41+
class="nhsuk-link nhsuk-link--no-visited-state js-check-in-link"
42+
data-clinic-id="{{ params.clinicId }}"
43+
data-event-id="{{ params.event.id }}"
44+
data-status-tag-id="{{ statusTagId }}">
45+
{{ checkInText | safe }}
46+
</a>
47+
{% endif %}
48+
</p>
49+
50+
51+
{% endif %}

0 commit comments

Comments
 (0)