Skip to content

Commit f557a66

Browse files
authored
Clinic overview options to present to SAIS teams in UR (#251)
This isn't intended to be the definitive set of dashboard panels; just something to talk around.
1 parent cc58423 commit f557a66

5 files changed

Lines changed: 182 additions & 4 deletions

File tree

app/models/session.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
Vaccine
2929
} from '../models.js'
3030
import {
31+
addDays,
3132
removeDays,
3233
convertIsoDateToObject,
3334
convertObjectToIsoDate,
@@ -359,6 +360,66 @@ export class Session {
359360
}
360361
}
361362

363+
/**
364+
* How many appointment slots (booked or otherwise) are there in this clinic session?
365+
*
366+
* @returns {number} - total number of appointment slots in this clinic session
367+
*/
368+
get totalAppointmentCount() {
369+
if (this.type !== SessionType.Clinic) {
370+
return 0
371+
}
372+
373+
if (!this.vaccinationPeriods?.length) {
374+
return 0
375+
}
376+
377+
return this.vaccinationPeriods
378+
.map((period) => period.appointmentCount(this.appointmentLength))
379+
.reduce((total, next) => total + next, 0)
380+
}
381+
382+
/**
383+
* How many appointment slots remain unbooked in this clinic session?
384+
*
385+
* @returns {number} - the number of appointment slots remaining in this clinic session
386+
*/
387+
get availableAppointmentCount() {
388+
return Math.floor(this.totalAppointmentCount * 0.1)
389+
}
390+
391+
/**
392+
* Get the number of days parents have left to book their child into this clinic
393+
*
394+
* @returns {number} - the number of days before appointment booking closes
395+
*/
396+
get daysLeftToBook() {
397+
if (this.status !== SessionStatus.Planned) {
398+
return 0
399+
}
400+
401+
// TODO: encode this assumption of closing booking 24 hours before the clinic into the booking process
402+
const cutOffDate = addDays(this.date, -1)
403+
404+
const millisecondsPerDay = 1000 * 60 * 60 * 24
405+
return Math.max(0, Math.floor((cutOffDate - today()) / millisecondsPerDay))
406+
}
407+
408+
/**
409+
* Get the percentage of the clinic session's run time that's available to book
410+
*
411+
* @returns {number} - the percentage of bookable time that's still available
412+
*/
413+
get availabilityPercentage() {
414+
const total = this.totalAppointmentCount
415+
if (total === 0) {
416+
return 0
417+
}
418+
419+
const available = this.availableAppointmentCount
420+
return Math.floor((available / total) * 100)
421+
}
422+
362423
/**
363424
* Get school
364425
*
@@ -594,7 +655,7 @@ export class Session {
594655
*/
595656
get name() {
596657
if (this.clinic) {
597-
return `${this.programmeNames.titleCase} community clinic on ${this.formatted.dateShort}`
658+
return `${this.programmeNames.titleCase} clinic at ${this.location.name} on ${this.formatted.dateShort}`
598659
}
599660

600661
if (this.location) {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{{ appHeading({
2+
title: "Appointments",
3+
level: 3,
4+
size: "m"
5+
}) }}
6+
7+
<div class="nhsuk-grid-row nhsuk-card-group">
8+
{% set columnWidth = "quarter" %}
9+
10+
{# Distribution chart elements: ▁▂▃▄▅▆▇█ https://rosettacode.org/wiki/Sparkline_in_unicode #}
11+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
12+
{{ appDataCard({
13+
heading: "Total appointments",
14+
headingLevel: 4,
15+
colour: "blue",
16+
compact: true,
17+
data: session.totalAppointmentCount,
18+
href: session.uri + "/edit"
19+
}) }}
20+
</div>
21+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
22+
{{ appDataCard({
23+
heading: "Available appointments",
24+
headingLevel: 4,
25+
colour: "blue",
26+
compact: true,
27+
data: session.availableAppointmentCount,
28+
href: session.uri + "/edit"
29+
}) }}
30+
</div>
31+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
32+
{{ appDataCard({
33+
heading: "Availability",
34+
headingLevel: 4,
35+
colour: "green" if session.availabilityPercentage > 25 else ("orange" if session.availabilityPercentage > 5 else "red"),
36+
compact: true,
37+
data: session.availabilityPercentage ~ "%",
38+
href: session.uri + "/edit"
39+
}) }}
40+
</div>
41+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
42+
{{ appDataCard({
43+
heading: "Days left to book",
44+
headingLevel: 4,
45+
colour: "blue",
46+
compact: true,
47+
data: session.daysLeftToBook,
48+
href: session.uri + "/edit"
49+
}) }}
50+
</div>
51+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
52+
{{ appDataCard({
53+
heading: "Start time",
54+
headingLevel: 4,
55+
compact: true,
56+
data: "9:00",
57+
href: session.uri + "/edit"
58+
}) }}
59+
</div>
60+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
61+
{{ appDataCard({
62+
heading: "End time",
63+
headingLevel: 4,
64+
compact: true,
65+
data: "15:00",
66+
href: session.uri + "/edit"
67+
}) }}
68+
</div>
69+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
70+
{{ appDataCard({
71+
heading: "Busiest hour",
72+
headingLevel: 4,
73+
colour: "orange",
74+
compact: true,
75+
data: "9:00",
76+
href: session.uri + "/appointments"
77+
}) }}
78+
</div>
79+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
80+
{{ appDataCard({
81+
heading: "Vaccinators",
82+
headingLevel: 4,
83+
colour: "blue",
84+
compact: true,
85+
data: "3",
86+
href: session.uri + "/edit"
87+
}) }}
88+
</div>
89+
{% for programme in session.programmes %}
90+
<div class="nhsuk-grid-column-one-{{ columnWidth }} nhsuk-card-group__item">
91+
{{ appDataCard({
92+
heading: programme.type + " appointments",
93+
headingLevel: 4,
94+
colour: "green",
95+
compact: true,
96+
data: "12",
97+
href: session.uri + "/report?programme_id=" + programme.id
98+
}) }}
99+
</div>
100+
{% endfor %}
101+
</div>

app/views/session/_session-programme-summary.njk

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,20 @@
130130
{% endmacro %}
131131

132132
{% set tabItems = [] %}
133+
134+
{% if session.type === SessionType.Clinic %}
135+
{% set planningHtml %}
136+
{% include "session/_session-clinic-summary.njk" %}
137+
{% endset %}
138+
{% set tabItems = tabItems | push({
139+
label: "Appointments",
140+
id: "Planning",
141+
panel: {
142+
html: planningHtml
143+
}
144+
}) %}
145+
{% endif %}
146+
133147
{% for programme in session.programmes %}
134148
{% set tabItems = tabItems | push({
135149
label: programme.name,
@@ -142,4 +156,4 @@
142156

143157
{{ tabs({
144158
items: tabItems
145-
}) if session.programmes.length > 1 else programmeHtml(session.programmes[0]) }}
159+
}) if session.type === SessionType.Clinic or session.programmes.length > 1 else programmeHtml(session.programmes[0]) }}

app/views/session/activity.njk

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
{% set title = session.shortName + "" + __("session." + view + ".title") %}
66
{% set heading = session.shortName %}
7-
{% set caption = session.formatted.date + "" + session.formatted.yearGroups %}
7+
{% set caption = session.formatted.date
8+
+ ("" + session.formatted.yearGroups if session.type === SessionType.School)
9+
%}
810

911
{% block content %}
1012
{% if (view == "record") and not session.isActive %}

app/views/session/show.njk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
headingLevel: 3
3131
},
3232
rows: summaryRows(session, {
33-
location: {} if session.type == SessionType.School,
33+
location: {},
3434
school_id: {},
3535
status: {},
3636
patients: {},

0 commit comments

Comments
 (0)