Skip to content

Commit ea23dd7

Browse files
committed
Implement programme status tallies for the new session overview pahe
The session overview page is getting a new design layout which focuses on giving greater details about the progress of a session using tallies across various metrics such Eligible cohort, Vaccinated, Could not vaccinate etc. The overview page no is longer divided into 2 columns, and instead split up into 5 sections: 1. Programme status 2. Consent status 3. Session dates 4. Action required 5. About the location This commit will start with Programme status. The new design will only activate if the URL has the query param "tallies=true". This gives the testers are chance to see and understand the new design as it's built out.
1 parent 605ffcd commit ea23dd7

File tree

8 files changed

+552
-87
lines changed

8 files changed

+552
-87
lines changed

app/assets/stylesheets/components/_card.scss

Lines changed: 106 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,83 @@
33

44
.app-card {
55
&--aqua-green {
6-
.nhsuk-card__heading--feature {
7-
background-color: nhsuk-colour("aqua-green");
6+
background-color: nhsuk-tint(nhsuk-colour("aqua-green"), 80%);
7+
border-color: nhsuk-tint(nhsuk-colour("aqua-green"), 50%);
8+
color: nhsuk-shade(nhsuk-colour("aqua-green"), 50%);
9+
10+
&.nhsuk-card__clickable:active {
11+
border-color: nhsuk-shade(nhsuk-colour("aqua-green"), 50%);
812
}
913
}
1014

11-
&--dark-orange {
12-
.nhsuk-card__heading--feature {
13-
background-color: app.$dark-orange-colour;
15+
&--blue {
16+
background-color: nhsuk-tint(nhsuk-colour("blue"), 80%);
17+
border-color: nhsuk-tint(nhsuk-colour("blue"), 30%);
18+
color: nhsuk-shade(nhsuk-colour("blue"), 30%);
19+
20+
&.nhsuk-card__clickable:active {
21+
border-color: nhsuk-shade(nhsuk-colour("blue"), 30%);
1422
}
1523
}
1624

1725
&--green {
18-
.nhsuk-card__heading--feature {
19-
background-color: nhsuk-colour("green");
26+
background-color: nhsuk-tint(nhsuk-colour("green"), 80%);
27+
border-color: nhsuk-tint(nhsuk-colour("green"), 40%);
28+
color: nhsuk-shade(nhsuk-colour("green"), 40%);
29+
30+
&.nhsuk-card__clickable:active {
31+
border-color: nhsuk-shade(nhsuk-colour("green"), 40%);
2032
}
2133
}
2234

2335
&--grey {
24-
.nhsuk-card__heading--feature {
25-
background-color: nhsuk-colour("grey-1");
26-
}
27-
}
36+
background-color: nhsuk-tint(nhsuk-colour("grey-1"), 80%);
37+
border-color: nhsuk-tint(nhsuk-colour("grey-1"), 30%);
38+
color: nhsuk-shade(nhsuk-colour("grey-1"), 30%);
2839

29-
&--purple {
30-
.nhsuk-card__heading--feature {
31-
background-color: nhsuk-colour("purple");
40+
&.nhsuk-card__clickable:active {
41+
border-color: nhsuk-shade(nhsuk-colour("grey-1"), 30%);
3242
}
3343
}
3444

3545
&--red {
36-
.nhsuk-card__heading--feature {
37-
background-color: nhsuk-colour("red");
46+
background-color: nhsuk-tint(nhsuk-colour("red"), 80%);
47+
border-color: nhsuk-tint(nhsuk-colour("red"), 50%);
48+
color: nhsuk-shade(nhsuk-colour("red"), 50%);
49+
50+
&.nhsuk-card__clickable:active {
51+
border-color: nhsuk-shade(nhsuk-colour("red"), 50%);
3852
}
3953
}
4054

41-
&--reversed {
42-
background-color: nhsuk-colour("blue");
43-
border-color: nhsuk-shade(nhsuk-colour("blue"), 25%);
44-
color: nhsuk-colour("white");
55+
&--yellow {
56+
background-color: nhsuk-tint(nhsuk-colour("warm-yellow"), 80%);
57+
border-color: nhsuk-colour("warm-yellow");
4558

46-
&:active {
47-
border-color: nhsuk-shade(nhsuk-colour("blue"), 50%);
59+
.nhsuk-card__heading--feature {
60+
background-color: nhsuk-colour("warm-yellow");
61+
color: $nhsuk-text-colour;
4862
}
4963

50-
.nhsuk-card__link {
51-
@include nhsuk-link-style-white;
64+
blockquote {
65+
border-color: nhsuk-colour("warm-yellow");
5266
}
5367
}
5468

55-
&--offset {
56-
background-color: rgba(nhsuk-colour("grey-5"), 0.5);
57-
}
58-
59-
&--data {
60-
display: flex;
61-
62-
.nhsuk-card__content {
63-
display: flex;
64-
flex-direction: column;
65-
flex-grow: 1;
66-
}
67-
68-
.nhsuk-card__heading {
69-
@include nhsuk-typography-weight-normal;
69+
&--aqua-green,
70+
&--blue,
71+
&--green,
72+
&--grey,
73+
&--red,
74+
&--yellow {
75+
.nhsuk-card__link,
76+
.nhsuk-card__link:hover {
77+
color: inherit;
7078
}
79+
}
7180

72-
.nhsuk-card__description {
73-
margin-top: auto;
74-
75-
@include nhsuk-font-size(48);
76-
@include nhsuk-typography-weight-bold;
77-
}
81+
&--offset {
82+
background-color: rgba(nhsuk-colour("grey-5"), 0.5);
7883
}
7984
}
8085

@@ -90,3 +95,59 @@
9095
@include nhsuk-responsive-padding(3, "bottom");
9196
}
9297
}
98+
99+
// Align counts across cards
100+
.nhsuk-card:has(> .nhsuk-card__content > .app-card__count) {
101+
display: flex;
102+
103+
.nhsuk-card__heading {
104+
line-height: 1.2;
105+
}
106+
107+
.nhsuk-card__content {
108+
display: flex;
109+
flex: 1;
110+
flex-direction: column;
111+
justify-content: space-between;
112+
}
113+
}
114+
115+
.app-card__count {
116+
@include nhsuk-font-size(48);
117+
@include nhsuk-typography-weight-bold;
118+
119+
.app-card--dense & {
120+
@include nhsuk-font-size(26);
121+
}
122+
}
123+
124+
.app-card__heading {
125+
&--aqua-green {
126+
background-color: nhsuk-colour("aqua-green");
127+
}
128+
129+
&--dark-orange {
130+
background-color: app.$dark-orange-colour;
131+
}
132+
133+
&--green {
134+
background-color: nhsuk-colour("green");
135+
}
136+
137+
&--grey {
138+
background-color: nhsuk-colour("grey-1");
139+
}
140+
141+
&--purple {
142+
background-color: nhsuk-colour("purple");
143+
}
144+
145+
&--red {
146+
background-color: nhsuk-colour("red");
147+
}
148+
149+
&--warm-yellow {
150+
background-color: nhsuk-colour("warm-yellow");
151+
color: $nhsuk-text-colour;
152+
}
153+
}

app/components/app_card_component.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class AppCardComponent < ViewComponent::Base
1717
<% end %>
1818
1919
<% if description.present? %>
20-
<p class="nhsuk-card__description"><%= description %></p>
20+
<p class="nhsuk-card__description <%= discription_classes %>"><%= description %></p>
2121
<% end %>
2222
2323
<%= content %>
@@ -36,7 +36,8 @@ def initialize(
3636
data: false,
3737
compact: false,
3838
filters: false,
39-
section: false
39+
section: false,
40+
style_as_count: false
4041
)
4142
@link_to = link_to
4243
@colour = colour
@@ -46,6 +47,7 @@ def initialize(
4647
@compact = compact
4748
@filters = filters
4849
@section = section
50+
@style_as_count = style_as_count
4951

5052
@feature = (colour.present? && !data && !compact) || filters
5153
end
@@ -94,4 +96,8 @@ def heading_classes
9496
("nhsuk-card__heading--feature" if @feature)
9597
].compact.join(" ")
9698
end
99+
100+
def discription_classes
101+
[("app-card__count" if @style_as_count)].compact.join(" ")
102+
end
97103
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<div class="nhsuk-grid-column-full">
2+
<% programmes.each do |programme| %>
3+
<section>
4+
<h3 class="nhsuk-heading-m nhsuk-u-margin-bottom-4">
5+
<%= programme.name %>
6+
</h3>
7+
<ul class="nhsuk-grid-row nhsuk-card-group">
8+
<% tally_cards_for_programme(programme).each do |card_data| %>
9+
<li class="nhsuk-grid-column-one-quarter nhsuk-card-group__item">
10+
<%= render AppCardComponent.new(
11+
heading_level: 5,
12+
colour: card_data[:colour],
13+
data: true,
14+
compact: true,
15+
style_as_count: true,
16+
link_to: card_data[:link_to],
17+
) do |card| %>
18+
<% card.with_heading { card_data[:heading] } %>
19+
<% card.with_description { card_data[:count] } %>
20+
<% end %>
21+
</li>
22+
<% end %>
23+
</ul>
24+
</section>
25+
<% end %>
26+
</div>
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# frozen_string_literal: true
2+
3+
class AppSessionOverviewTalliesComponent < ViewComponent::Base
4+
def initialize(session)
5+
@session = session
6+
@patient_ids = session.patient_ids
7+
@academic_year = session.academic_year
8+
end
9+
10+
attr_reader :session, :patient_ids, :academic_year
11+
12+
delegate :govuk_table, to: :helpers
13+
delegate :programmes, to: :session
14+
15+
def tally_cards_for_programme(programme)
16+
[
17+
{
18+
heading: "Eligible cohort",
19+
colour: "blue",
20+
count: eligible_for_vaccination_count(programme).to_s,
21+
link_to: nil
22+
},
23+
{
24+
heading: "Vaccinated",
25+
colour: "green",
26+
count: vaccinated_count(programme).to_s,
27+
link_to:
28+
session_patients_path(
29+
@session,
30+
vaccination_status: "vaccinated",
31+
programme_types: [programme.type]
32+
)
33+
},
34+
{
35+
heading: "Could not vaccinate",
36+
colour: "red",
37+
count: could_not_vaccinate_count(programme).to_s,
38+
link_to:
39+
session_patients_path(
40+
@session,
41+
vaccination_status: "could_not_vaccinate",
42+
programme_types: [programme.type]
43+
)
44+
},
45+
{
46+
heading: "No outcome",
47+
colour: "grey",
48+
count: no_outcome_count(programme).to_s,
49+
link_to:
50+
session_patients_path(
51+
@session,
52+
vaccination_status: "none_yet",
53+
programme_types: [programme.type]
54+
)
55+
}
56+
]
57+
end
58+
59+
private
60+
61+
def eligible_for_vaccination_count(programme)
62+
patients_in_programme_cohort(programme).count -
63+
previously_vaccinated_count(programme) -
64+
vaccinated_at_different_locations_count(programme)
65+
end
66+
67+
def vaccinated_count(programme)
68+
administered_vaccination_count(programme)
69+
end
70+
71+
def could_not_vaccinate_count(programme)
72+
Patient::VaccinationStatus
73+
.where(programme:, academic_year:, patient_id: patient_ids)
74+
.could_not_vaccinate
75+
.count
76+
end
77+
78+
def no_outcome_count(programme)
79+
Patient::VaccinationStatus
80+
.where(programme:, academic_year:, patient_id: patient_ids)
81+
.none_yet
82+
.count
83+
end
84+
85+
def patients_in_programme_cohort(programme)
86+
session.patients.appear_in_programmes(programme, academic_year:)
87+
end
88+
89+
def previously_vaccinated_count(programme)
90+
return 0 if programme.seasonal?
91+
92+
Patient::VaccinationStatus
93+
.where(programme_id: programme.id, patient_id: patient_ids)
94+
.where("academic_year < ?", academic_year)
95+
.vaccinated
96+
.count
97+
end
98+
99+
def administered_vaccination_count(programme)
100+
Patient::VaccinationStatus
101+
.where(
102+
academic_year:,
103+
programme_id: programme.id,
104+
patient_id: patient_ids
105+
)
106+
.vaccinated
107+
.count
108+
end
109+
110+
def vaccinated_at_different_locations_count(programme)
111+
vaccination_records =
112+
VaccinationRecord
113+
.where(
114+
patient_id: patient_ids,
115+
programme_id: programme.id,
116+
outcome: %w[administered already_had]
117+
)
118+
.where.not(location: session.location)
119+
120+
# The VaccinatedCriteria class is responsible for determining whether a
121+
# patient is considered vaccinated or not. There's some nuance around
122+
# Td/IPV and MenACWY where a patient may have a vaccination record, but
123+
# if it's the wrong dose or if the patient was younger than 10 years old
124+
# it doesn't count.
125+
session.patients.count do |patient|
126+
VaccinatedCriteria.call(
127+
programme:,
128+
academic_year:,
129+
patient:,
130+
vaccination_records:
131+
)
132+
end
133+
end
134+
end

0 commit comments

Comments
 (0)