Skip to content

Commit 1ffedaa

Browse files
January updates after round 3 of usability testing
* Add notification banner * Show remaining appointments by default * Update medical information * Lots of other small changes
1 parent cc4ccff commit 1ffedaa

31 files changed

+950
-712
lines changed

app/assets/sass/_misc.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@
99
}
1010
}
1111

12-
1312
.app-suppress-link-styles * {
1413
text-decoration: none;
1514
color: $nhsuk-secondary-text-color;
1615
}
1716

18-
1917
.app-image-flip-horizontal {
2018
-webkit-transform: scaleX(-1);
2119
transform: scaleX(-1);
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Adapted from https://github.com/alphagov/govuk-frontend/blob/main/packages/govuk-frontend/src/govuk/components/notification-banner/_index.scss
2+
3+
$nhsuk-success-color: $color_nhsuk-green !default;
4+
5+
/// Success link styles
6+
///
7+
/// Makes links use the success colour. The link will darken if it's active or a
8+
/// user hovers their cursor over it.
9+
///
10+
/// If you use this mixin in a component, you must also include the
11+
/// `govuk-link-common` mixin to get the correct focus and hover states.
12+
///
13+
/// @example scss
14+
/// .govuk-component__link {
15+
/// @include govuk-link-common;
16+
/// @include govuk-link-style-success;
17+
/// }
18+
///
19+
/// @access public
20+
21+
@mixin nhsuk-link-style-success {
22+
&:link,
23+
&:visited {
24+
color: $nhsuk-success-color;
25+
}
26+
27+
&:hover {
28+
color: scale-color($nhsuk-success-color, $lightness: -30%);
29+
}
30+
31+
&:active {
32+
color: $nhsuk-success-color;
33+
}
34+
35+
// When focussed, the text colour needs to be darker to ensure that colour
36+
// contrast is still acceptable
37+
&:focus {
38+
color: $nhsuk-focus-text-color;
39+
}
40+
}
41+
42+
.app-notification-banner {
43+
@include nhsuk-font($size: 19);
44+
@include nhsuk-responsive-margin(8, "bottom");
45+
46+
border: $nhsuk-border-width solid $color_nhsuk-blue;
47+
48+
background-color: $color_nhsuk-blue;
49+
50+
&:focus {
51+
outline: $nhsuk-focus-width solid $nhsuk-focus-color;
52+
}
53+
}
54+
55+
.app-notification-banner__header {
56+
padding: 2px nhsuk-spacing(3) nhsuk-spacing(1);
57+
58+
// Ensures the notification header appears separate to the notification body
59+
// text in high contrast mode
60+
border-bottom: 1px solid transparent;
61+
62+
@include govuk-media-query($from: tablet) {
63+
padding: 2px nhsuk-spacing(4) nhsuk-spacing(1);
64+
}
65+
}
66+
67+
.app-notification-banner__title {
68+
// Set the size again because this element is a heading and the user agent
69+
// font size overrides the inherited font size
70+
@include nhsuk-font($size: 19);
71+
@include nhsuk-typography-weight-bold;
72+
margin: 0;
73+
padding: 0;
74+
color: $color_nhsuk-white;
75+
}
76+
77+
.app-notification-banner__content {
78+
$padding-tablet: nhsuk-spacing(4);
79+
color: $nhsuk-text-color;
80+
padding: nhsuk-spacing(3);
81+
82+
background-color: $color_nhsuk-white;
83+
84+
@include govuk-media-query($from: tablet) {
85+
padding: $padding-tablet;
86+
}
87+
88+
// Wrap content at the same place that a 2/3 grid column ends, to maintain
89+
// shorter line-lengths when the notification banner is full width
90+
> * {
91+
// When elements have their own padding (like lists), include the padding
92+
// in the max-width calculation
93+
box-sizing: border-box;
94+
95+
// Calculate the internal width of a two-thirds column...
96+
$two-col-width: ($nhsuk-page-width * 2 / 3) - ($nhsuk-gutter * 1 / 3);
97+
98+
// ...and then factor in the left border and padding
99+
$banner-exterior: ($padding-tablet + $nhsuk-border-width);
100+
max-width: $two-col-width - $banner-exterior;
101+
}
102+
103+
> :last-child {
104+
margin-bottom: 0;
105+
}
106+
}
107+
108+
.app-notification-banner__heading {
109+
@include nhsuk-font($size: 24);
110+
@include nhsuk-typography-weight-bold;
111+
112+
margin: 0 0 nhsuk-spacing(3);
113+
114+
padding: 0;
115+
}
116+
117+
.app-notification-banner__link {
118+
@include nhsuk-link-style-default;
119+
@include nhsuk-link-style-no-visited-state;
120+
}
121+
122+
.app-notification-banner--success {
123+
border-color: $nhsuk-success-color;
124+
background-color: $nhsuk-success-color;
125+
126+
.app-notification-banner__link {
127+
@include nhsuk-link-style-success;
128+
}
129+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.app-header-with-status {
2+
position: relative;
3+
}
4+
5+
.app-header-with-status__status-tag {
6+
position: absolute;
7+
top: 10px;
8+
right: 0px;
9+
text-align: right;
10+
}
11+

app/assets/sass/main.scss

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
@import 'components/header-organisation';
99
@import 'components/secondary-navigation';
1010
@import 'components/count';
11+
@import 'components/notification-banner';
12+
@import 'components/status';
13+
1114
@import 'components/overrides';
1215

13-
@import 'pages/events';
16+
1417

1518
@import 'misc';
1619

@@ -21,4 +24,4 @@
2124
///////////////////////////////////////////
2225
// Add your custom CSS/Sass styles below...
2326
///////////////////////////////////////////
24-
27+

app/assets/sass/pages/_events.scss

Lines changed: 0 additions & 10 deletions
This file was deleted.

app/lib/utils/dates.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ const formatDate = (dateString, format = 'D MMMM YYYY') => {
3333
return dayjs(dateString).format(format)
3434
}
3535

36+
/**
37+
* Format a date in UK format with special month abbreviations
38+
* For months with 4 letters (June, July), use the full name
39+
* For other months, use 3 letter abbreviation
40+
* @param {string} dateString - ISO date string
41+
* @returns {string} Formatted date string
42+
*/
43+
const formatDateShort = (dateString) => {
44+
if (!dateString) return ''
45+
46+
const date = dayjs(dateString)
47+
const monthFormat = ['June', 'July'].includes(date.format('MMMM')) ? 'MMMM' : 'MMM'
48+
49+
return `${date.format('D')} ${date.format(monthFormat)} ${date.format('YYYY')}`
50+
}
51+
3652
/**
3753
* Format a time in UK format
3854
* @param {string} dateString - ISO date string
@@ -44,9 +60,9 @@ const formatTime = (dateString, format = 'H:mm') => {
4460
}
4561

4662
/**
47-
* Format a time in 12-hour format
63+
* Format a time in 12-hour format with special cases for round hours, midday and midnight
4864
* @param {string} input - Either a time string (e.g. "17:00") or full date/datetime
49-
* @returns {string} Formatted time (e.g. "5:00pm")
65+
* @returns {string} Formatted time (e.g. "5pm", "5:30pm", "midday", "midnight")
5066
*/
5167
const formatTimeString = (input) => {
5268
if (!input) return ''
@@ -56,7 +72,19 @@ const formatTimeString = (input) => {
5672
? input
5773
: `2000-01-01T${input}`
5874

59-
return dayjs(datetime).format('h:mma')
75+
const time = dayjs(datetime)
76+
const hour = time.hour()
77+
const minute = time.minute()
78+
79+
// Handle special cases
80+
if (minute === 0) {
81+
if (hour === 0) return 'midnight'
82+
if (hour === 12) return 'midday'
83+
return `${time.format('h')}${time.format('a')}`
84+
}
85+
86+
// For non-zero minutes, return full format
87+
return time.format('h:mma')
6088
}
6189

6290
/**
@@ -217,6 +245,7 @@ const getWeekDates = (dateString) => {
217245

218246
module.exports = {
219247
formatDate,
248+
formatDateShort,
220249
formatTime,
221250
formatTimeString,
222251
formatTimeRange,

app/lib/utils/status.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ const filterEventsByStatus = (events, filter) => {
5252
return events.filter(e => e.status === 'event_checked_in')
5353
case 'complete':
5454
return events.filter(e => ['event_complete', 'event_partially_screened', 'event_attended_not_screened'].includes(e.status))
55+
case 'remaining':
56+
return events.filter(e => ['event_scheduled', 'event_checked_in'].includes(e.status))
5557
default:
5658
return events
5759
}

app/lib/utils/strings.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
// app/lib/utils/strings.js
22

33
/**
4-
* Convert string to sentence case
4+
* Convert string to sentence case, removing leading/trailing whitespace
55
* @param {string} input - String to convert
6-
* @returns {string} Sentence case string
6+
* @returns {string} Trimmed sentence case string
77
*/
88
const sentenceCase = (input) => {
99
if (!input) return ''
1010
if (typeof input !== 'string') return input
11-
return input.charAt(0).toUpperCase() + input.slice(1)
11+
12+
const trimmed = input.trim()
13+
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1)
1214
}
1315

1416
/**

app/lib/utils/summary-list.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// app/lib/utils/summary-list.js
2+
3+
const _ = require('lodash')
4+
5+
/**
6+
* Check if a value should be considered empty/missing
7+
* @param {any} value - Value to check
8+
* @returns {boolean} True if value should be considered empty
9+
*/
10+
const isEmpty = (value) => {
11+
if (_.isString(value)) {
12+
return value.trim() === '' || value.toLowerCase() === 'incomplete'
13+
}
14+
return value === null || value === undefined
15+
}
16+
17+
/**
18+
* Convert value object to "Enter X" link if empty
19+
* @param {Object} row - Summary list row
20+
* @returns {Object} Modified row with enter link if empty
21+
*/
22+
const showMissingInformationLink = (summaryList) => {
23+
if (!summaryList?.rows) return summaryList
24+
25+
const updatedRows = summaryList.rows.map(row => {
26+
const value = row.value?.text || row.value?.html
27+
if (!isEmpty(value)) return row
28+
29+
const keyText = row.actions?.items?.[0]?.visuallyHiddenText || row.key.text.toLowerCase()
30+
const href = row.actions?.items?.[0]?.href || '#'
31+
32+
33+
return {
34+
...row,
35+
value: {
36+
html: `<a href="${href}" class="nhsuk-link">Enter ${keyText}</a>`
37+
// html: `<a href="${href}" class="nhsuk-link">Enter details</a>`
38+
},
39+
actions: {
40+
items: []
41+
}
42+
}
43+
})
44+
45+
return {
46+
...summaryList,
47+
rows: updatedRows
48+
}
49+
}
50+
51+
module.exports = {
52+
showMissingInformationLink
53+
}

app/routes/clinics.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ module.exports = router => {
268268

269269

270270
// Single clinic view
271-
const VALID_FILTERS = ['scheduled', 'checked-in', 'complete', 'all']
271+
const VALID_FILTERS = ['remaining', 'scheduled', 'checked-in', 'complete', 'all']
272272

273273
// Support both /clinics/:id and /clinics/:id/:filter
274274
router.get(['/clinics/:id', '/clinics/:id/:filter'], (req, res) => {
@@ -279,7 +279,7 @@ module.exports = router => {
279279
}
280280

281281
// Check filter from either URL param or query string
282-
const filter = req.params.filter || req.query.filter || 'all'
282+
const filter = req.params.filter || req.query.filter || 'remaining'
283283

284284
// Validate filter
285285
if (!VALID_FILTERS.includes(filter)) {

0 commit comments

Comments
 (0)