Skip to content

Commit 9d5b452

Browse files
Update screened appointment state (#208)
* Update screened appointment state * Update app/views/events/appointment.html Co-authored-by: Ed Horsford <[email protected]> * Add duration for other end points where appointment is started and stopped, get data saving on attended not screened, small fixes * Fix bug * Update comment for record-keeping --------- Co-authored-by: Ed Horsford <[email protected]>
1 parent 25c2539 commit 9d5b452

File tree

3 files changed

+111
-30
lines changed

3 files changed

+111
-30
lines changed

app/lib/utils/dates.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,44 @@ const isWithinDayRange = (
678678
return isOlderThanMin && isYoungerThanMax
679679
}
680680

681+
/**
682+
* Calculate duration between two times in minutes (rounded up)
683+
*
684+
* @param {string} startTime - Start time (e.g. "09:00", "2024-01-01T09:00:00")
685+
* @param {string} endTime - End time (e.g. "10:30", "2024-01-01T10:30:00")
686+
* @returns {number} Duration in minutes (rounded up), or 0 if invalid
687+
* @example
688+
* calculateDurationMinutes("09:00", "10:30") // returns 90
689+
* calculateDurationMinutes("09:15", "09:45") // returns 30
690+
*/
691+
const calculateDurationMinutes = (startTime, endTime) => {
692+
if (!startTime || !endTime) return 0
693+
694+
// If it looks like just a time (contains no date), prefix with dummy date
695+
const startDatetime = startTime.includes('T') || startTime.includes('-')
696+
? startTime
697+
: `2000-01-01T${startTime}`
698+
699+
const endDatetime = endTime.includes('T') || endTime.includes('-')
700+
? endTime
701+
: `2000-01-01T${endTime}`
702+
703+
const start = dayjs(startDatetime)
704+
const end = dayjs(endDatetime)
705+
706+
// Validate both times are valid
707+
if (!start.isValid() || !end.isValid()) return 0
708+
709+
// Calculate difference in minutes
710+
const minutes = end.diff(start, 'minute', true)
711+
712+
// Return 0 for negative durations (end before start)
713+
if (minutes < 0) return 0
714+
715+
// Round up to nearest minute
716+
return Math.ceil(minutes)
717+
}
718+
681719
/**
682720
* Add or subtract time from a date
683721
*
@@ -766,6 +804,7 @@ module.exports = {
766804
dayjs,
767805
daysSince,
768806
isWithinDayRange,
807+
calculateDurationMinutes,
769808
add,
770809
remove
771810
}

app/routes/events.js

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@ function getEventData(data, clinicId, eventId) {
6363
}
6464
}
6565

66+
/**
67+
* Capture session end time for an event
68+
* @param {object} data - Session data
69+
* @param {string} eventId - Event ID
70+
* @param {string} userId - User ID ending the session
71+
*/
72+
function captureSessionEndTime(data, eventId, userId) {
73+
const event = getEvent(data, eventId)
74+
updateEventData(data, eventId, {
75+
sessionDetails: {
76+
...event.sessionDetails,
77+
endedAt: new Date().toISOString(),
78+
endedBy: userId
79+
}
80+
})
81+
}
82+
6683
// // Update event status and add to history
6784
// function updateEventStatus (event, newStatus) {
6885
// return {
@@ -1389,6 +1406,12 @@ module.exports = (router) => {
13891406
)
13901407
}
13911408

1409+
// Capture session end time only if appointment was started
1410+
const event = getEvent(data, eventId)
1411+
if (event?.sessionDetails?.startedAt) {
1412+
captureSessionEndTime(data, eventId, data.currentUser.id)
1413+
}
1414+
13921415
// Save the data
13931416
saveTempEventToEvent(data)
13941417
saveTempParticipantToParticipant(data)
@@ -2396,8 +2419,19 @@ module.exports = (router) => {
23962419
return
23972420
}
23982421

2422+
// Capture session end time only if appointment was started
2423+
const event = getEvent(data, eventId)
2424+
if (event?.sessionDetails?.startedAt) {
2425+
captureSessionEndTime(data, eventId, data.currentUser.id)
2426+
}
2427+
23992428
// If yes, redirect to reschedule page
24002429
if (needsReschedule === 'yes') {
2430+
// Save the appointmentStopped data before redirecting
2431+
saveTempEventToEvent(data)
2432+
saveTempParticipantToParticipant(data)
2433+
updateEventStatus(data, eventId, 'event_attended_not_screened')
2434+
24012435
res.redirect(
24022436
`/clinics/${clinicId}/events/${eventId}/cancel-or-reschedule-appointment/reschedule`
24032437
)
@@ -2430,9 +2464,19 @@ module.exports = (router) => {
24302464
const { clinicId, eventId } = req.params
24312465

24322466
const data = req.session.data
2467+
const currentUser = data.currentUser
24332468
const participantName = getFullName(data.participant)
24342469
const participantEventUrl = `/clinics/${clinicId}/events/${eventId}`
24352470

2471+
// Store session end details
2472+
updateEventData(data, eventId, {
2473+
sessionDetails: {
2474+
...getEvent(data, eventId).sessionDetails,
2475+
endedAt: new Date().toISOString(),
2476+
endedBy: currentUser.id
2477+
}
2478+
})
2479+
24362480
saveTempEventToEvent(data)
24372481
saveTempParticipantToParticipant(data)
24382482
updateEventStatus(data, eventId, 'event_complete')
@@ -2606,6 +2650,12 @@ module.exports = (router) => {
26062650
`/clinics/${clinicId}/events/${eventId}/cancel-or-reschedule-appointment/reschedule`
26072651
)
26082652
} else {
2653+
// Capture session end time only if appointment was started
2654+
const event = getEvent(data, eventId)
2655+
if (event?.sessionDetails?.startedAt) {
2656+
captureSessionEndTime(data, eventId, data.currentUser.id)
2657+
}
2658+
26092659
// Save the cancellation data
26102660
saveTempEventToEvent(data)
26112661
saveTempParticipantToParticipant(data)
@@ -2653,6 +2703,12 @@ module.exports = (router) => {
26532703
)
26542704
}
26552705

2706+
// Capture session end time only if appointment was started (appointments might be rescheduled over the phone or that never started)
2707+
const event = getEvent(data, eventId)
2708+
if (event?.sessionDetails?.startedAt) {
2709+
captureSessionEndTime(data, eventId, data.currentUser.id)
2710+
}
2711+
26562712
// Save the reschedule data
26572713
saveTempEventToEvent(data)
26582714
saveTempParticipantToParticipant(data)
@@ -2763,4 +2819,4 @@ module.exports = (router) => {
27632819
)
27642820
res.redirect(returnUrl)
27652821
})
2766-
}
2822+
}

app/views/events/appointment.html

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{% set showNavigation = true %}
77
{% set activeTab = 'appointment' %}
88
{% set formAction = "./start" %}
9-
{% set allowEdits = event.status != "event_cancelled" and event.status != "event_rescheduled" %}
9+
{% set allowEdits = not (event | isFinal) %}
1010

1111

1212
{% block pageContent %}
@@ -29,6 +29,13 @@
2929
})}}
3030
{% endif %}
3131

32+
{% if event | isFinal and event.sessionDetails %}
33+
{% set durationMinutes = event.sessionDetails.startedAt | calculateDurationMinutes(event.sessionDetails.endedAt) %}
34+
<p class="nhsuk-u-margin-top-2">
35+
Started at {{ event.sessionDetails.startedAt | formatDate('h:mma') }} and ended at {{ event.sessionDetails.endedAt | formatDate('h:mma') }}<br>({{ durationMinutes }} minutes)
36+
</p>
37+
{% endif %}
38+
3239
{% if event.status === "event_attended_not_screened" and event.appointmentStopped %}
3340
<p class="nhsuk-u-margin-top-2">
3441
<strong>Reason for stopping:</strong> {% for reason in event.appointmentStopped.stoppedReason -%}
@@ -121,8 +128,8 @@
121128
{% endset %}
122129

123130
{% set appointmentTimeHtml -%}
124-
<p>{{ clinic.date | formatDate }} ({{ clinic.date | formatRelativeDate }})</p>
125-
<p>{{ event.timing.startTime | formatTimeString }} ({{ event.timing.duration }} minutes)</p>
131+
{{ clinic.date | formatDate }} ({{ clinic.date | formatRelativeDate }})<br>
132+
{{ event.timing.startTime | formatTimeString }} ({{ event.timing.duration }} minutes)
126133
{% endset %}
127134

128135
{% set specialAppointmentHtml %}
@@ -141,15 +148,13 @@
141148
rows: [
142149
{
143150
key: {
144-
text: "Date"
151+
text: "Scheduled date and time"
145152
},
146153
value: {
147154
html: appointmentTimeHtml
148155
},
149156
actions: {
150-
items: [
151-
152-
]
157+
items: []
153158
}
154159
},
155160
{
@@ -160,9 +165,7 @@
160165
text: clinic.clinicType | formatWords | sentenceCase
161166
},
162167
actions: {
163-
items: [
164-
165-
]
168+
items: []
166169
}
167170
},
168171
{
@@ -208,26 +211,9 @@
208211
text: "Change",
209212
visuallyHiddenText: "special appointment"
210213
}
211-
]
212-
}
213-
},
214-
{
215-
key: {
216-
text: "Appointment notes"
217-
},
218-
value: {
219-
html: ''
220-
},
221-
actions: {
222-
items: [
223-
{
224-
href: "#",
225-
text: "Change",
226-
visuallyHiddenText: "appointment notes"
227-
}
228-
]
214+
] if event.status != "event_complete" else []
229215
}
230-
} if false
216+
}
231217
]
232218
} | handleSummaryListMissingInformation) }}
233219
{% endset %}

0 commit comments

Comments
 (0)