@ministryofjustice/hmpps-connect-dps-shared-items 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -11
- package/dist/addressAutosuggest/osPlacesAddressService.d.ts +14 -0
- package/dist/addressAutosuggest/osPlacesAddressService.test.d.ts +1 -0
- package/dist/addressAutosuggest/osPlacesApiClient.d.ts +13 -0
- package/dist/addressAutosuggest/osPlacesApiClient.test.d.ts +1 -0
- package/dist/addressAutosuggest/testMocks/osPlacesAddressQueryResponse.d.ts +12 -0
- package/dist/addressAutosuggest/testMocks/osPlacesApiClientMock.d.ts +2 -0
- package/dist/assets/dps/components/address-autosuggest/_address-autosuggest.scss +98 -0
- package/dist/assets/dps/components/address-autosuggest/abortable-fetch.js +43 -0
- package/dist/assets/dps/components/address-autosuggest/autosuggest-address.js +90 -0
- package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.js +369 -0
- package/dist/assets/dps/components/address-autosuggest/index.js +10 -0
- package/dist/assets/dps/components/address-autosuggest/macro.njk +61 -0
- package/dist/assets/js/all.js +5 -0
- package/dist/assets/scss/all.scss +2 -1
- package/dist/index.cjs +128 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +169 -2
- package/dist/index.esm.js +127 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/types/public/ConnectDpsComponentLogger.d.ts +2 -0
- package/dist/types/public/addressAutosuggest/index.d.ts +2 -0
- package/dist/types/public/addressAutosuggest/osAddress.d.ts +13 -0
- package/dist/types/public/addressAutosuggest/osPlaces.d.ts +124 -0
- package/dist/utils/utils.d.ts +8 -0
- package/package.json +10 -2
- package/readme/publishing.md +3 -3
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// Based on ONSDigital's https://github.com/ONSdigital/design-system/blob/main/src/components/autosuggest/autosuggest.ui.js
|
|
2
|
+
import { abortError, abortTimeout } from './abortable-fetch'
|
|
3
|
+
|
|
4
|
+
const jsBaseClass = 'hmpps-js-autosuggest'
|
|
5
|
+
|
|
6
|
+
const classSuffixInitialised = '--initialised'
|
|
7
|
+
const classSuffixOption = '__option'
|
|
8
|
+
const classSuffixOptionFocused = `${classSuffixOption}--focused`
|
|
9
|
+
const classSuffixOptionNoResults = `${classSuffixOption}--no-results`
|
|
10
|
+
const classSuffixHasResults = '--has-results'
|
|
11
|
+
|
|
12
|
+
export const NoResults = {
|
|
13
|
+
type_more: 'TYPE_MORE',
|
|
14
|
+
no_match: 'NO_MATCH',
|
|
15
|
+
server_error: 'SERVER_ERROR',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class AutosuggestUi {
|
|
19
|
+
constructor({ context, onSelect, suggestionFunction }) {
|
|
20
|
+
// DOM Elements
|
|
21
|
+
this.context = context
|
|
22
|
+
this.input = context.querySelector(`.${jsBaseClass}-input`)
|
|
23
|
+
this.resultsContainer = context.querySelector(`.${jsBaseClass}-results`)
|
|
24
|
+
this.resultsTitleContainer = this.resultsContainer.querySelector(`.${jsBaseClass}-results-title`)
|
|
25
|
+
this.listbox = this.resultsContainer.querySelector(`.${jsBaseClass}-listbox`)
|
|
26
|
+
this.instructions = context.querySelector(`.${jsBaseClass}-instructions`)
|
|
27
|
+
this.errorContainer = context.querySelector(`.${jsBaseClass}-error`)
|
|
28
|
+
this.errorTextContainer = this.errorContainer.querySelector('.moj-alert__content')
|
|
29
|
+
this.ariaStatus = context.querySelector(`.${jsBaseClass}-aria-status`)
|
|
30
|
+
|
|
31
|
+
// Settings
|
|
32
|
+
this.minChars = 3
|
|
33
|
+
this.ariaYouHaveSelected = context.getAttribute('data-aria-you-have-selected')
|
|
34
|
+
this.ariaMinChars = context.getAttribute('data-aria-min-chars')
|
|
35
|
+
this.ariaOneResult = context.getAttribute('data-aria-one-result')
|
|
36
|
+
this.ariaNResults = context.getAttribute('data-aria-n-results')
|
|
37
|
+
this.noResults = context.getAttribute('data-no-results')
|
|
38
|
+
this.typeMore = context.getAttribute('data-type-more')
|
|
39
|
+
this.listboxId = this.listbox.getAttribute('id')
|
|
40
|
+
this.errorMessage = context.getAttribute('data-error-api')
|
|
41
|
+
this.stylingBaseClass = context.getAttribute('data-styling-base-class')
|
|
42
|
+
|
|
43
|
+
// Callbacks
|
|
44
|
+
this.onSelect = onSelect
|
|
45
|
+
this.fetchSuggestions = suggestionFunction
|
|
46
|
+
|
|
47
|
+
// State
|
|
48
|
+
this.query = ''
|
|
49
|
+
this.sanitisedQuery = ''
|
|
50
|
+
this.results = []
|
|
51
|
+
this.resultOptions = []
|
|
52
|
+
this.data = []
|
|
53
|
+
this.numberOfResults = 0
|
|
54
|
+
this.highlightedResultIndex = 0
|
|
55
|
+
this.blurring = false
|
|
56
|
+
this.blurTimeout = null
|
|
57
|
+
this.scrolling = false
|
|
58
|
+
this.scrollTimeout = null
|
|
59
|
+
|
|
60
|
+
this.initialiseUI()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
initialiseUI() {
|
|
64
|
+
this.input.setAttribute('aria-autocomplete', 'list')
|
|
65
|
+
this.input.setAttribute('aria-controls', this.listbox.getAttribute('id'))
|
|
66
|
+
this.input.setAttribute('aria-describedby', this.instructions.getAttribute('id'))
|
|
67
|
+
this.input.setAttribute('aria-haspopup', true)
|
|
68
|
+
this.input.setAttribute('aria-owns', this.listbox.getAttribute('id'))
|
|
69
|
+
this.input.setAttribute('aria-expanded', false)
|
|
70
|
+
this.input.setAttribute('autocomplete', 'off')
|
|
71
|
+
this.input.setAttribute('role', 'combobox')
|
|
72
|
+
|
|
73
|
+
this.context.classList.add(`${this.stylingBaseClass}${classSuffixInitialised}`)
|
|
74
|
+
|
|
75
|
+
this.bindEventListeners()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
bindEventListeners() {
|
|
79
|
+
this.input.addEventListener('keydown', this.handleKeydown.bind(this))
|
|
80
|
+
this.input.addEventListener('keyup', this.handleKeyup.bind(this))
|
|
81
|
+
this.input.addEventListener('input', this.handleChange.bind(this))
|
|
82
|
+
this.input.addEventListener('blur', this.handleBlur.bind(this))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
handleKeydown(event) {
|
|
86
|
+
switch (event.keyCode) {
|
|
87
|
+
case 38: {
|
|
88
|
+
// Up
|
|
89
|
+
event.preventDefault()
|
|
90
|
+
this.navigateResults(-1)
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
case 40: {
|
|
94
|
+
// Down
|
|
95
|
+
event.preventDefault()
|
|
96
|
+
this.navigateResults(1)
|
|
97
|
+
break
|
|
98
|
+
}
|
|
99
|
+
case 13: {
|
|
100
|
+
// Enter
|
|
101
|
+
if (this.highlightedResultIndex !== null) {
|
|
102
|
+
event.preventDefault()
|
|
103
|
+
}
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
handleKeyup(event) {
|
|
110
|
+
switch (event.keyCode) {
|
|
111
|
+
// Up and down
|
|
112
|
+
case 40:
|
|
113
|
+
case 38: {
|
|
114
|
+
event.preventDefault()
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
case 13: {
|
|
118
|
+
// Enter
|
|
119
|
+
if (this.highlightedResultIndex !== null) {
|
|
120
|
+
this.selectResult()
|
|
121
|
+
}
|
|
122
|
+
break
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
handleChange() {
|
|
128
|
+
if (!this.blurring && this.input.value.trim()) {
|
|
129
|
+
this.getSuggestions()
|
|
130
|
+
} else {
|
|
131
|
+
this.clearListbox()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
handleBlur() {
|
|
136
|
+
clearTimeout(this.blurTimeout)
|
|
137
|
+
this.blurring = true
|
|
138
|
+
|
|
139
|
+
// Timeout required to allow user to click an option before clearing the listbox:
|
|
140
|
+
this.blurTimeout = setTimeout(() => {
|
|
141
|
+
this.blurring = false
|
|
142
|
+
this.clearListbox()
|
|
143
|
+
}, 300)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
handleScroll(option, scrollUp) {
|
|
147
|
+
clearTimeout(this.scrollTimeout)
|
|
148
|
+
this.scrolling = true
|
|
149
|
+
option.scrollIntoView(scrollUp)
|
|
150
|
+
|
|
151
|
+
// Timeout required to allow user to click an option before clearing the listbox:
|
|
152
|
+
this.scrollTimeout = setTimeout(() => {
|
|
153
|
+
this.scrolling = false
|
|
154
|
+
}, 300)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
checkCharCount() {
|
|
158
|
+
if (this.input.value.length > 1 && this.input.value.length < this.minChars) {
|
|
159
|
+
this.inputTimeout = setTimeout(() => {
|
|
160
|
+
this.handleNoResults(NoResults.type_more)
|
|
161
|
+
}, 2000)
|
|
162
|
+
} else {
|
|
163
|
+
clearTimeout(this.inputTimeout)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
navigateResults(direction) {
|
|
168
|
+
let index = 0
|
|
169
|
+
if (this.highlightedResultIndex !== null) {
|
|
170
|
+
index = this.highlightedResultIndex + direction
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (index < this.numberOfResults) {
|
|
174
|
+
if (index < 0) {
|
|
175
|
+
index = null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.setHighlightedResult(index)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
sanitiseAutosuggestText(string) {
|
|
183
|
+
let sanitisedString = string.toLowerCase()
|
|
184
|
+
|
|
185
|
+
sanitisedString = sanitisedString.trim()
|
|
186
|
+
sanitisedString = sanitisedString.replace(/,/g, '')
|
|
187
|
+
sanitisedString = sanitisedString.replace(/\s\s+/g, ' ')
|
|
188
|
+
sanitisedString = sanitisedString.replace(/[&]/g, '%26')
|
|
189
|
+
sanitisedString = sanitisedString.replace(/\d(?=[a-z]{3,})/gi, '$& ')
|
|
190
|
+
|
|
191
|
+
return sanitisedString
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
getSuggestions() {
|
|
195
|
+
this.query = this.input.value
|
|
196
|
+
|
|
197
|
+
const sanitisedQuery = this.sanitiseAutosuggestText(this.query)
|
|
198
|
+
|
|
199
|
+
if (sanitisedQuery !== this.sanitisedQuery) {
|
|
200
|
+
this.sanitisedQuery = sanitisedQuery
|
|
201
|
+
this.unsetResults()
|
|
202
|
+
this.checkCharCount()
|
|
203
|
+
|
|
204
|
+
if (this.sanitisedQuery.length >= this.minChars) {
|
|
205
|
+
this.fetchSuggestions(this.sanitisedQuery, this.data)
|
|
206
|
+
.then(this.handleResults.bind(this))
|
|
207
|
+
.catch(error => {
|
|
208
|
+
if (error.name !== abortError && error.reason !== abortTimeout) {
|
|
209
|
+
console.log('error:', error)
|
|
210
|
+
this.handleNoResults(NoResults.server_error)
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
} else {
|
|
214
|
+
this.clearListbox()
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
unsetResults() {
|
|
220
|
+
this.results = []
|
|
221
|
+
this.resultOptions = []
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
clearListbox() {
|
|
225
|
+
this.listbox.innerHTML = ''
|
|
226
|
+
this.context.classList.remove(`${this.stylingBaseClass}${classSuffixHasResults}`)
|
|
227
|
+
this.input.removeAttribute('aria-activedescendant')
|
|
228
|
+
this.input.setAttribute('aria-expanded', false)
|
|
229
|
+
this.setAriaStatus()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
handleResults(result) {
|
|
233
|
+
this.results = result.results
|
|
234
|
+
this.numberOfResults = this.results?.length || 0
|
|
235
|
+
this.setAriaStatus()
|
|
236
|
+
this.listbox.innerHTML = ''
|
|
237
|
+
|
|
238
|
+
this.resultOptions =
|
|
239
|
+
this.results?.map((result, index) => {
|
|
240
|
+
this.resultsTitleContainer?.classList?.remove('hmpps-display-none')
|
|
241
|
+
const listElement = this.createListElement(result, index)
|
|
242
|
+
this.listbox.appendChild(listElement)
|
|
243
|
+
return listElement
|
|
244
|
+
}) ?? []
|
|
245
|
+
|
|
246
|
+
this.setHighlightedResult(0)
|
|
247
|
+
this.input.setAttribute('aria-expanded', this.numberOfResults ? 'true' : 'false')
|
|
248
|
+
|
|
249
|
+
if (!!this.numberOfResults && this.sanitisedQuery.length >= this.minChars) {
|
|
250
|
+
this.context.classList.add(`${this.stylingBaseClass}${classSuffixHasResults}`)
|
|
251
|
+
} else {
|
|
252
|
+
this.context.classList.remove(`${this.stylingBaseClass}${classSuffixHasResults}`)
|
|
253
|
+
this.clearListbox()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (this.numberOfResults === 0 && this.noResults) {
|
|
257
|
+
this.handleNoResults(NoResults.no_match)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
createListElement(result, index) {
|
|
262
|
+
const innerHTML = this.emboldenMatch(result.displayText, this.query)
|
|
263
|
+
const listElement = document.createElement('li')
|
|
264
|
+
listElement.className = `${this.stylingBaseClass}${classSuffixOption}`
|
|
265
|
+
listElement.setAttribute('id', `${this.listboxId}__option--${index}`)
|
|
266
|
+
listElement.setAttribute('role', 'option')
|
|
267
|
+
listElement.innerHTML = innerHTML
|
|
268
|
+
listElement.addEventListener('click', () => {
|
|
269
|
+
this.selectResult(index)
|
|
270
|
+
})
|
|
271
|
+
listElement.addEventListener('mouseenter', () => !this.scrolling && this.setHighlightedResult(index))
|
|
272
|
+
return listElement
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
handleNoResults(reason) {
|
|
276
|
+
if ([NoResults.type_more, NoResults.no_match].includes(reason)) {
|
|
277
|
+
this.context.classList.add(`${this.stylingBaseClass}${classSuffixHasResults}`)
|
|
278
|
+
this.resultsTitleContainer?.classList?.add('hmpps-display-none')
|
|
279
|
+
this.input.setAttribute('aria-expanded', true)
|
|
280
|
+
|
|
281
|
+
const message = reason === NoResults.type_more ? this.typeMore : this.noResults
|
|
282
|
+
this.setAriaStatus(message)
|
|
283
|
+
this.listbox.innerHTML = `<li class="${this.stylingBaseClass}${classSuffixOption} ${this.stylingBaseClass}${classSuffixOptionNoResults}">${message}</li>`
|
|
284
|
+
} else {
|
|
285
|
+
this.displayAPIError()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
displayAPIError() {
|
|
290
|
+
this.input.value = ''
|
|
291
|
+
this.input.setAttribute('disabled', true)
|
|
292
|
+
this.clearListbox()
|
|
293
|
+
this.errorContainer.classList.remove('hmpps-display-none')
|
|
294
|
+
this.errorTextContainer.innerHTML = this.errorMessage
|
|
295
|
+
this.ariaStatus.setAttribute('aria-hidden', 'true')
|
|
296
|
+
this.setAriaStatus(this.errorMessage)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
setHighlightedResult(index) {
|
|
300
|
+
this.highlightedResultIndex = index
|
|
301
|
+
|
|
302
|
+
if (this.highlightedResultIndex === null) {
|
|
303
|
+
this.input.removeAttribute('aria-activedescendant')
|
|
304
|
+
} else if (this.numberOfResults) {
|
|
305
|
+
this.resultOptions.forEach((option, optionIndex) => {
|
|
306
|
+
if (optionIndex === index) {
|
|
307
|
+
if (this.isAboveViewport(option)) {
|
|
308
|
+
this.handleScroll(option, true)
|
|
309
|
+
}
|
|
310
|
+
if (this.isBelowViewport(option)) {
|
|
311
|
+
this.handleScroll(option, false)
|
|
312
|
+
}
|
|
313
|
+
option.classList.add(`${this.stylingBaseClass}${classSuffixOptionFocused}`)
|
|
314
|
+
option.setAttribute('aria-selected', true)
|
|
315
|
+
this.input.setAttribute('aria-activedescendant', option.getAttribute('id'))
|
|
316
|
+
const optionText = option.innerHTML.replace('<strong>', '').replace('</strong>', '')
|
|
317
|
+
this.setAriaStatus(optionText)
|
|
318
|
+
} else {
|
|
319
|
+
option.classList.remove(`${this.stylingBaseClass}${classSuffixOptionFocused}`)
|
|
320
|
+
option.removeAttribute('aria-selected')
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
selectResult(index) {
|
|
327
|
+
if (!this.results.length) return
|
|
328
|
+
|
|
329
|
+
const result = this.results[index || this.highlightedResultIndex || 0]
|
|
330
|
+
this.onSelect(result)
|
|
331
|
+
this.clearListbox()
|
|
332
|
+
this.setAriaStatus(`${this.ariaYouHaveSelected}: ${result.displayText}.`)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
setAriaStatus(content) {
|
|
336
|
+
if (!content) {
|
|
337
|
+
const queryTooShort = this.sanitisedQuery.length < this.minChars
|
|
338
|
+
const noResults = this.numberOfResults === 0
|
|
339
|
+
if (queryTooShort) {
|
|
340
|
+
content = this.ariaMinChars
|
|
341
|
+
} else if (noResults) {
|
|
342
|
+
content = `${this.noResults}: "${this.query}"`
|
|
343
|
+
} else if (this.numberOfResults === 1) {
|
|
344
|
+
content = this.ariaOneResult
|
|
345
|
+
} else {
|
|
346
|
+
content = this.ariaNResults.replace('{n}', this.numberOfResults)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
this.ariaStatus.innerHTML = content
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
emboldenMatch(string, query) {
|
|
353
|
+
let reg = new RegExp(this.escapeRegExp(query).split(' ').join('[\\s,]*'), 'gi')
|
|
354
|
+
|
|
355
|
+
return string.replace(reg, '<strong>$&</strong>')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
escapeRegExp(string) {
|
|
359
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
isAboveViewport(element) {
|
|
363
|
+
return element.getBoundingClientRect().top < 0
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
isBelowViewport(element) {
|
|
367
|
+
return element.getBoundingClientRect().bottom > (window.innerHeight || document.documentElement.clientHeight)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AutosuggestAddress } from './autosuggest-address'
|
|
2
|
+
|
|
3
|
+
export function init() {
|
|
4
|
+
// Setup all address autosuggest instances:
|
|
5
|
+
const addressAutosuggests = [...document.querySelectorAll('.hmpps-js-address-autosuggest')]
|
|
6
|
+
|
|
7
|
+
if (addressAutosuggests.length) {
|
|
8
|
+
addressAutosuggests.forEach(addressAutosuggest => new AutosuggestAddress(addressAutosuggest))
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{% from "govuk/components/input/macro.njk" import govukInput %}
|
|
2
|
+
{% from "moj/components/alert/macro.njk" import mojAlert %}
|
|
3
|
+
|
|
4
|
+
{% macro addressAutosuggest(options) %}
|
|
5
|
+
|
|
6
|
+
<div class="hmpps-js-address-autosuggest">
|
|
7
|
+
<input type="hidden" id="uprn" class="hmpps-js-uprn hmpps-display-none" name="uprn"/>
|
|
8
|
+
<div
|
|
9
|
+
class="hmpps-address-autosuggest{% if options.classes %} {{ options.classes }}{% endif %}"
|
|
10
|
+
data-aria-you-have-selected="You have selected"
|
|
11
|
+
data-aria-min-chars="Enter 3 or more characters for suggestions."
|
|
12
|
+
data-aria-one-result="There is one suggestion available."
|
|
13
|
+
data-aria-n-results="There are {n} suggestions available."
|
|
14
|
+
data-no-results="No results found. Try entering a different part of the address"
|
|
15
|
+
data-type-more="Enter more of the address to get results"
|
|
16
|
+
data-error-api="The address search is not working right now. Enter the address manually to continue."
|
|
17
|
+
data-styling-base-class="hmpps-address-autosuggest"
|
|
18
|
+
data-find-url="{{ options.findUrl or "/api/addresses/find" }}"
|
|
19
|
+
>
|
|
20
|
+
{{ govukInput({
|
|
21
|
+
id: 'address-autosuggest-input',
|
|
22
|
+
name: 'address-autosuggest-input',
|
|
23
|
+
classes: 'hmpps-js-autosuggest-input',
|
|
24
|
+
value: options.inputValue,
|
|
25
|
+
attributes: {
|
|
26
|
+
'autocomplete': 'off',
|
|
27
|
+
'aria-describedby': 'address-hint'
|
|
28
|
+
},
|
|
29
|
+
errorMessage: options.errorMessage
|
|
30
|
+
}) }}
|
|
31
|
+
<div class="hmpps-address-autosuggest__results hmpps-js-autosuggest-results">
|
|
32
|
+
<div id="address-results" class="hmpps-address-autosuggest__results-title hmpps-js-autosuggest-results-title">
|
|
33
|
+
Select an address
|
|
34
|
+
</div>
|
|
35
|
+
<ul
|
|
36
|
+
class="hmpps-address-autosuggest__listbox hmpps-js-autosuggest-listbox"
|
|
37
|
+
title="Select an address"
|
|
38
|
+
role="listbox"
|
|
39
|
+
id="address-autosuggest-listbox"
|
|
40
|
+
tabindex="-1"></ul>
|
|
41
|
+
</div>
|
|
42
|
+
<div
|
|
43
|
+
class="hmpps-address-autosuggest__instructions hmpps-js-autosuggest-instructions govuk-visually-hidden"
|
|
44
|
+
id="address-autosuggest-instructions"
|
|
45
|
+
tabindex="-1">
|
|
46
|
+
Use up and down keys to navigate suggestions once you have typed more than two characters. Use the enter key to select a
|
|
47
|
+
suggestion. Touch device users, explore by touch or with swipe gestures.
|
|
48
|
+
</div>
|
|
49
|
+
<div
|
|
50
|
+
class="hmpps-address-autosuggest__status hmpps-js-autosuggest-aria-status govuk-visually-hidden "
|
|
51
|
+
aria-live="assertive"
|
|
52
|
+
aria-atomic="true"
|
|
53
|
+
role="status"
|
|
54
|
+
tabindex="-1"></div>
|
|
55
|
+
{{ mojAlert({
|
|
56
|
+
variant: "warning",
|
|
57
|
+
classes: 'hmpps-address-autosuggest__error hmpps-js-autosuggest-error hmpps-display-none'
|
|
58
|
+
}) }}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
{% endmacro %}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
@import '../dps/components/alert-flag/alert-flag';
|
|
1
|
+
@import '../dps/components/alert-flag/alert-flag';
|
|
2
|
+
@import '../dps/components/address-autosuggest/address-autosuggest';
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,131 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var Fuse = require('fuse.js');
|
|
4
|
+
var superagent = require('superagent');
|
|
5
|
+
|
|
6
|
+
const isBlank = (str) => !str || /^\s*$/.test(str);
|
|
7
|
+
const properCase = (word) => word.length >= 1 ? word[0].toUpperCase() + word.toLowerCase().slice(1) : word;
|
|
8
|
+
/**
|
|
9
|
+
* Converts a name (first name, last name, middle name, etc.) to proper case equivalent, handling double-barreled names
|
|
10
|
+
* correctly (i.e. each part in a double-barreled is converted to proper case).
|
|
11
|
+
* @param name name to be converted.
|
|
12
|
+
* @returns name converted to proper case.
|
|
13
|
+
*/
|
|
14
|
+
const properCaseName = (name) => (isBlank(name) ? '' : name.split('-').map(properCase).join('-'));
|
|
15
|
+
const convertToTitleCase = (sentence) => isBlank(sentence) ? '' : sentence.split(' ').map(properCaseName).join(' ');
|
|
16
|
+
|
|
17
|
+
const simplePostCodeRegex = /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/i;
|
|
18
|
+
const stringContainingPostCodeRegex = /^(.*?)([A-Z]{1,2}\d[A-Z\d]? ?)(\d[A-Z]{2})(.*)$/i;
|
|
19
|
+
class OsPlacesAddressService {
|
|
20
|
+
logger;
|
|
21
|
+
osPlacesApiClient;
|
|
22
|
+
constructor(logger, osPlacesApiClient) {
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
this.osPlacesApiClient = osPlacesApiClient;
|
|
25
|
+
}
|
|
26
|
+
async getAddressesMatchingQuery(searchQuery, sanitisePostcode = true) {
|
|
27
|
+
const response = await this.osPlacesApiClient.getAddressesByFreeTextQuery(sanitisePostcode ? this.sanitiseUkPostcode(searchQuery) : searchQuery);
|
|
28
|
+
const rawResults = this.handleResponse(response);
|
|
29
|
+
const bestMatchResults = new Fuse(rawResults, {
|
|
30
|
+
shouldSort: true,
|
|
31
|
+
threshold: 0.2,
|
|
32
|
+
ignoreLocation: true,
|
|
33
|
+
keys: [{ name: 'addressString' }],
|
|
34
|
+
})
|
|
35
|
+
.search(searchQuery)
|
|
36
|
+
.map(result => result.item);
|
|
37
|
+
const queryIsAPostCode = simplePostCodeRegex.test(searchQuery.trim());
|
|
38
|
+
const buildingNumberSort = (a, b) => a?.addressString && b?.addressString && queryIsAPostCode
|
|
39
|
+
? a.addressString.localeCompare(b.addressString, undefined, { numeric: true, sensitivity: 'base' })
|
|
40
|
+
: 1;
|
|
41
|
+
// maximum number of properties in a postcode is 100
|
|
42
|
+
return (bestMatchResults.length ? bestMatchResults : rawResults).sort(buildingNumberSort).slice(0, 100);
|
|
43
|
+
}
|
|
44
|
+
async getAddressByUprn(uprn) {
|
|
45
|
+
const response = await this.osPlacesApiClient.getAddressesByUprn(uprn);
|
|
46
|
+
const result = this.handleResponse(response);
|
|
47
|
+
if (result.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
if (result.length > 1)
|
|
50
|
+
this.logger.info(`Multiple results returned for UPRN`);
|
|
51
|
+
return result[result.length - 1];
|
|
52
|
+
}
|
|
53
|
+
sanitiseUkPostcode(stringContainingPostcode) {
|
|
54
|
+
const postCodeQuery = stringContainingPostCodeRegex.exec(stringContainingPostcode?.replace(/[^A-Z0-9 ]/gi, ''));
|
|
55
|
+
if (!postCodeQuery)
|
|
56
|
+
return stringContainingPostcode;
|
|
57
|
+
return `${postCodeQuery[1]}${postCodeQuery[2].toUpperCase().trim()} ${postCodeQuery[3].toUpperCase().trim()}${postCodeQuery[4]}`;
|
|
58
|
+
}
|
|
59
|
+
handleResponse(response) {
|
|
60
|
+
if (response.header && response.header.totalresults === 0)
|
|
61
|
+
return [];
|
|
62
|
+
return response.results.filter(result => result.DPA).map(result => this.toOsAddress(result.DPA));
|
|
63
|
+
}
|
|
64
|
+
toOsAddress(addressResult) {
|
|
65
|
+
const { UPRN, DEPENDENT_LOCALITY, SUB_BUILDING_NAME, BUILDING_NAME, BUILDING_NUMBER, THOROUGHFARE_NAME, POST_TOWN, POSTCODE, COUNTRY_CODE, LOCAL_CUSTODIAN_CODE_DESCRIPTION, } = addressResult;
|
|
66
|
+
return {
|
|
67
|
+
addressString: this.formatAddressString(addressResult),
|
|
68
|
+
buildingNumber: BUILDING_NUMBER,
|
|
69
|
+
buildingName: convertToTitleCase(BUILDING_NAME),
|
|
70
|
+
subBuildingName: convertToTitleCase(SUB_BUILDING_NAME),
|
|
71
|
+
thoroughfareName: convertToTitleCase(THOROUGHFARE_NAME),
|
|
72
|
+
dependentLocality: convertToTitleCase(DEPENDENT_LOCALITY),
|
|
73
|
+
postTown: convertToTitleCase(POST_TOWN),
|
|
74
|
+
county: convertToTitleCase(LOCAL_CUSTODIAN_CODE_DESCRIPTION),
|
|
75
|
+
postcode: POSTCODE,
|
|
76
|
+
country: COUNTRY_CODE,
|
|
77
|
+
uprn: UPRN,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
formatAddressString(addressResult) {
|
|
81
|
+
const { ADDRESS, BUILDING_NUMBER, THOROUGHFARE_NAME, POSTCODE } = addressResult;
|
|
82
|
+
const withoutPostcode = ADDRESS.replace(`, ${POSTCODE}`, '').replace(`${BUILDING_NUMBER}, ${THOROUGHFARE_NAME}`, `${BUILDING_NUMBER} ${THOROUGHFARE_NAME}`);
|
|
83
|
+
return `${convertToTitleCase(withoutPostcode)}, ${POSTCODE}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class OsPlacesApiClient {
|
|
88
|
+
logger;
|
|
89
|
+
config;
|
|
90
|
+
constructor(logger, config) {
|
|
91
|
+
this.logger = logger;
|
|
92
|
+
this.config = config;
|
|
93
|
+
}
|
|
94
|
+
async getAddressesByFreeTextQuery(freeTextQuery) {
|
|
95
|
+
const queryParams = {
|
|
96
|
+
query: freeTextQuery,
|
|
97
|
+
lr: 'EN',
|
|
98
|
+
key: this.config.apiKey,
|
|
99
|
+
};
|
|
100
|
+
return this.get('/find', queryParams);
|
|
101
|
+
}
|
|
102
|
+
async getAddressesByUprn(uprn) {
|
|
103
|
+
const queryParams = {
|
|
104
|
+
uprn,
|
|
105
|
+
key: this.config.apiKey,
|
|
106
|
+
};
|
|
107
|
+
return this.get('/uprn', queryParams);
|
|
108
|
+
}
|
|
109
|
+
async get(path, query) {
|
|
110
|
+
const endpoint = `${this.config.url}${path}`;
|
|
111
|
+
try {
|
|
112
|
+
const result = await superagent
|
|
113
|
+
.get(endpoint)
|
|
114
|
+
.retry(2, (err, res) => {
|
|
115
|
+
if (err)
|
|
116
|
+
this.logger.info(`Retry handler found API error with ${err.code} ${err.message}`);
|
|
117
|
+
return undefined; // retry handler only for logging retries, not to influence retry logic
|
|
118
|
+
})
|
|
119
|
+
.query(query)
|
|
120
|
+
.timeout(this.config.timeout);
|
|
121
|
+
return result.body;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw Error(`Error calling OS Places API: ${error.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
3
129
|
const alertFlagLabels = [
|
|
4
130
|
{ alertCodes: ['HA'], classes: 'dps-alert-status dps-alert-status--self-harm', label: 'ACCT open' },
|
|
5
131
|
{
|
|
@@ -145,5 +271,7 @@ function getAlertFlagLabelsForAlerts(prisonerAlerts) {
|
|
|
145
271
|
}, []);
|
|
146
272
|
}
|
|
147
273
|
|
|
274
|
+
exports.OsPlacesAddressService = OsPlacesAddressService;
|
|
275
|
+
exports.OsPlacesApiClient = OsPlacesApiClient;
|
|
148
276
|
exports.getAlertFlagLabelsForAlerts = getAlertFlagLabelsForAlerts;
|
|
149
277
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/alertFlags/getAlertFlagLabelsForAlerts.ts"],"sourcesContent":["import { Alert, AlertsServiceAlert } from '../types/public/alertFlags/Alert'\nimport { AlertFlagLabel } from '../types/public/alertFlags/AlertFlagLabel'\n\nexport const alertFlagLabels = [\n { alertCodes: ['HA'], classes: 'dps-alert-status dps-alert-status--self-harm', label: 'ACCT open' },\n {\n alertCodes: ['HA1'],\n classes: 'dps-alert-status dps-alert-status--self-harm',\n label: 'ACCT post closure',\n },\n {\n alertCodes: ['XSA', 'SA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Staff assaulter',\n },\n {\n alertCodes: ['XA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Arsonist',\n },\n {\n alertCodes: ['PEEP'],\n classes: 'dps-alert-status dps-alert-status--medical',\n label: 'PEEP',\n },\n {\n alertCodes: ['HID'],\n classes: 'dps-alert-status dps-alert-status--medical',\n label: 'Hidden disability',\n },\n { alertCodes: ['XEL'], classes: 'dps-alert-status dps-alert-status--security', label: 'E-list' },\n { alertCodes: ['XELH'], classes: 'dps-alert-status dps-alert-status--security', label: 'E-list heightened' },\n { alertCodes: ['XER'], classes: 'dps-alert-status dps-alert-status--security', label: 'Escape risk' },\n {\n alertCodes: ['XRF'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Risk to females',\n },\n { alertCodes: ['XTACT'], classes: 'dps-alert-status dps-alert-status--security', label: 'TACT' },\n {\n alertCodes: ['XCO'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Corruptor',\n },\n {\n alertCodes: ['XCA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Chemical attacker',\n },\n {\n alertCodes: ['XCI'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Concerted indiscipline',\n },\n { alertCodes: ['XR'], classes: 'dps-alert-status dps-alert-status--security', label: 'Racist' },\n {\n alertCodes: ['RTP', 'RLG'],\n classes: 'dps-alert-status dps-alert-status--risk',\n label: 'Risk to LGBT',\n },\n {\n alertCodes: ['XHT'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Hostage taker',\n },\n {\n alertCodes: ['XCU'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Controlled unlock',\n },\n {\n alertCodes: ['XGANG'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Gang member',\n },\n { alertCodes: ['CSIP'], classes: 'dps-alert-status dps-alert-status--other', label: 'CSIP' },\n { alertCodes: ['F1'], classes: 'dps-alert-status dps-alert-status--ex-armed-forces', label: 'Veteran' },\n {\n alertCodes: ['LCE'],\n classes: 'dps-alert-status dps-alert-status--care-leaver',\n label: 'Care experienced',\n },\n {\n alertCodes: ['RNO121'],\n classes: 'dps-alert-status dps-alert-status--risk',\n label: 'No one-to-one',\n },\n { alertCodes: ['RCON'], classes: 'dps-alert-status dps-alert-status--risk', label: 'Conflict' },\n {\n alertCodes: ['RCDR'],\n classes: 'dps-alert-status dps-alert-status--quarantined',\n label: 'Quarantined',\n },\n {\n alertCodes: ['URCU'],\n classes: 'dps-alert-status dps-alert-status--reverse-cohorting-unit',\n label: 'Reverse Cohorting Unit',\n },\n {\n alertCodes: ['UPIU'],\n classes: 'dps-alert-status dps-alert-status--protective-isolation-unit',\n label: 'Protective Isolation Unit',\n },\n { alertCodes: ['USU'], classes: 'dps-alert-status dps-alert-status--shielding-unit', label: 'Shielding Unit' },\n {\n alertCodes: ['URS'],\n classes: 'dps-alert-status dps-alert-status--refusing-to-shield',\n label: 'Refusing to shield',\n },\n {\n alertCodes: ['RKS'],\n classes: 'dps-alert-status dps-alert-status--risk-to-known-adults',\n label: 'Risk to known adults',\n },\n { alertCodes: ['VIP'], classes: 'dps-alert-status dps-alert-status--isolated-prisoner', label: 'Isolated' },\n {\n alertCodes: ['PVN'],\n classes: 'dps-alert-status dps-alert-status--multicase dps-alert-status--visor',\n label: 'ViSOR',\n },\n {\n alertCodes: ['XCDO'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Involved in 2024 civil disorder',\n },\n {\n alertCodes: ['XVL'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Violent',\n },\n].sort((a, b) => a.label.localeCompare(b.label))\n\nfunction isAlertsServiceAlert(alert: Alert): alert is AlertsServiceAlert {\n return typeof alert.alertCode !== 'string'\n}\n\nexport default function getAlertFlagLabelsForAlerts(prisonerAlerts: Alert[]): AlertFlagLabel[] {\n return alertFlagLabels.reduce(\n (acc: AlertFlagLabel[], flag: { alertCodes: string[]; classes: string; label: string }) => {\n const alertIds = prisonerAlerts\n .filter(alert => {\n const alertsServiceAlert = isAlertsServiceAlert(alert)\n const alertIsActive = alertsServiceAlert ? alert.isActive : alert.active && !alert.expired\n const prisonerAlertCode = alertsServiceAlert ? alert.alertCode.code : alert.alertCode\n\n return alertIsActive && flag.alertCodes.includes(prisonerAlertCode)\n })\n .map(alert => (isAlertsServiceAlert(alert) ? (alert.alertUuid as string) : alert.alertCode))\n\n return alertIds.length ? [...acc, { ...flag, alertIds }] : acc\n },\n [] as AlertFlagLabel[],\n )\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/utils/utils.ts","../src/addressAutosuggest/osPlacesAddressService.ts","../src/addressAutosuggest/osPlacesApiClient.ts","../src/alertFlags/getAlertFlagLabelsForAlerts.ts"],"sourcesContent":["const isBlank = (str: string): boolean => !str || /^\\s*$/.test(str)\n\nconst properCase = (word: string): string =>\n word.length >= 1 ? word[0].toUpperCase() + word.toLowerCase().slice(1) : word\n\n/**\n * Converts a name (first name, last name, middle name, etc.) to proper case equivalent, handling double-barreled names\n * correctly (i.e. each part in a double-barreled is converted to proper case).\n * @param name name to be converted.\n * @returns name converted to proper case.\n */\nexport const properCaseName = (name: string): string => (isBlank(name) ? '' : name.split('-').map(properCase).join('-'))\n\nexport const convertToTitleCase = (sentence: string): string =>\n isBlank(sentence) ? '' : sentence.split(' ').map(properCaseName).join(' ')\n","import Fuse from 'fuse.js'\nimport { OsAddress } from '../types/public/addressAutosuggest/osAddress'\nimport { convertToTitleCase } from '../utils/utils'\nimport OsPlacesApiClient from './osPlacesApiClient'\nimport { ConnectDpsComponentLogger } from '../types/public/ConnectDpsComponentLogger'\nimport { OsPlacesDeliveryPointAddress, OsPlacesQueryResponse } from '../types/public/addressAutosuggest/osPlaces'\n\nconst simplePostCodeRegex = /^[A-Z]{1,2}\\d[A-Z\\d]? ?\\d[A-Z]{2}$/i\nconst stringContainingPostCodeRegex = /^(.*?)([A-Z]{1,2}\\d[A-Z\\d]? ?)(\\d[A-Z]{2})(.*)$/i\n\nexport default class OsPlacesAddressService {\n constructor(\n private readonly logger: ConnectDpsComponentLogger,\n private readonly osPlacesApiClient: OsPlacesApiClient,\n ) {}\n\n public async getAddressesMatchingQuery(searchQuery: string, sanitisePostcode: boolean = true): Promise<OsAddress[]> {\n const response = await this.osPlacesApiClient.getAddressesByFreeTextQuery(\n sanitisePostcode ? this.sanitiseUkPostcode(searchQuery) : searchQuery,\n )\n\n const rawResults = this.handleResponse(response)\n\n const bestMatchResults = new Fuse(rawResults, {\n shouldSort: true,\n threshold: 0.2,\n ignoreLocation: true,\n keys: [{ name: 'addressString' }],\n })\n .search(searchQuery)\n .map(result => result.item)\n\n const queryIsAPostCode = simplePostCodeRegex.test(searchQuery.trim())\n const buildingNumberSort = (a: OsAddress, b: OsAddress) =>\n a?.addressString && b?.addressString && queryIsAPostCode\n ? a.addressString.localeCompare(b.addressString, undefined, { numeric: true, sensitivity: 'base' })\n : 1\n\n // maximum number of properties in a postcode is 100\n return (bestMatchResults.length ? bestMatchResults : rawResults).sort(buildingNumberSort).slice(0, 100)\n }\n\n public async getAddressByUprn(uprn: string): Promise<OsAddress | null> {\n const response = await this.osPlacesApiClient.getAddressesByUprn(uprn)\n const result = this.handleResponse(response)\n\n if (result.length === 0) return null\n if (result.length > 1) this.logger.info(`Multiple results returned for UPRN`)\n\n return result[result.length - 1]\n }\n\n public sanitiseUkPostcode(stringContainingPostcode: string) {\n const postCodeQuery = stringContainingPostCodeRegex.exec(stringContainingPostcode?.replace(/[^A-Z0-9 ]/gi, ''))\n if (!postCodeQuery) return stringContainingPostcode\n\n return `${postCodeQuery[1]}${postCodeQuery[2].toUpperCase().trim()} ${postCodeQuery[3].toUpperCase().trim()}${postCodeQuery[4]}`\n }\n\n private handleResponse(response: OsPlacesQueryResponse): OsAddress[] {\n if (response.header && response.header.totalresults === 0) return []\n\n return response.results.filter(result => result.DPA).map(result => this.toOsAddress(result.DPA!))\n }\n\n private toOsAddress(addressResult: OsPlacesDeliveryPointAddress): OsAddress {\n const {\n UPRN,\n DEPENDENT_LOCALITY,\n SUB_BUILDING_NAME,\n BUILDING_NAME,\n BUILDING_NUMBER,\n THOROUGHFARE_NAME,\n POST_TOWN,\n POSTCODE,\n COUNTRY_CODE,\n LOCAL_CUSTODIAN_CODE_DESCRIPTION,\n } = addressResult\n\n return {\n addressString: this.formatAddressString(addressResult),\n buildingNumber: BUILDING_NUMBER,\n buildingName: convertToTitleCase(BUILDING_NAME),\n subBuildingName: convertToTitleCase(SUB_BUILDING_NAME),\n thoroughfareName: convertToTitleCase(THOROUGHFARE_NAME),\n dependentLocality: convertToTitleCase(DEPENDENT_LOCALITY),\n postTown: convertToTitleCase(POST_TOWN),\n county: convertToTitleCase(LOCAL_CUSTODIAN_CODE_DESCRIPTION),\n postcode: POSTCODE,\n country: COUNTRY_CODE,\n uprn: UPRN,\n }\n }\n\n private formatAddressString(addressResult: OsPlacesDeliveryPointAddress) {\n const { ADDRESS, BUILDING_NUMBER, THOROUGHFARE_NAME, POSTCODE } = addressResult\n const withoutPostcode = ADDRESS.replace(`, ${POSTCODE}`, '').replace(\n `${BUILDING_NUMBER}, ${THOROUGHFARE_NAME}`,\n `${BUILDING_NUMBER} ${THOROUGHFARE_NAME}`,\n )\n return `${convertToTitleCase(withoutPostcode)}, ${POSTCODE}`\n }\n}\n","import superagent from 'superagent'\nimport { ApiConfig } from '@ministryofjustice/hmpps-rest-client'\nimport { OsPlacesQueryResponse } from '../types/public/addressAutosuggest/osPlaces'\nimport { ConnectDpsComponentLogger } from '../types/public/ConnectDpsComponentLogger'\n\nexport default class OsPlacesApiClient {\n constructor(\n private readonly logger: ConnectDpsComponentLogger,\n private readonly config: ApiConfig & { apiKey: string },\n ) {}\n\n async getAddressesByFreeTextQuery(freeTextQuery: string): Promise<OsPlacesQueryResponse> {\n const queryParams: Record<string, string> = {\n query: freeTextQuery,\n lr: 'EN',\n key: this.config.apiKey,\n }\n\n return this.get<Promise<OsPlacesQueryResponse>>('/find', queryParams)\n }\n\n async getAddressesByUprn(uprn: string): Promise<OsPlacesQueryResponse> {\n const queryParams: Record<string, string> = {\n uprn,\n key: this.config.apiKey,\n }\n\n return this.get<Promise<OsPlacesQueryResponse>>('/uprn', queryParams)\n }\n\n async get<T>(path: string, query: Record<string, string>): Promise<T> {\n const endpoint = `${this.config.url}${path}`\n try {\n const result = await superagent\n .get(endpoint)\n .retry(2, (err, res) => {\n if (err) this.logger.info(`Retry handler found API error with ${err.code} ${err.message}`)\n return undefined // retry handler only for logging retries, not to influence retry logic\n })\n .query(query)\n .timeout(this.config.timeout)\n\n return result.body\n } catch (error: any) {\n throw Error(`Error calling OS Places API: ${error.message}`)\n }\n }\n}\n","import { Alert, AlertsServiceAlert } from '../types/public/alertFlags/Alert'\nimport { AlertFlagLabel } from '../types/public/alertFlags/AlertFlagLabel'\n\nexport const alertFlagLabels = [\n { alertCodes: ['HA'], classes: 'dps-alert-status dps-alert-status--self-harm', label: 'ACCT open' },\n {\n alertCodes: ['HA1'],\n classes: 'dps-alert-status dps-alert-status--self-harm',\n label: 'ACCT post closure',\n },\n {\n alertCodes: ['XSA', 'SA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Staff assaulter',\n },\n {\n alertCodes: ['XA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Arsonist',\n },\n {\n alertCodes: ['PEEP'],\n classes: 'dps-alert-status dps-alert-status--medical',\n label: 'PEEP',\n },\n {\n alertCodes: ['HID'],\n classes: 'dps-alert-status dps-alert-status--medical',\n label: 'Hidden disability',\n },\n { alertCodes: ['XEL'], classes: 'dps-alert-status dps-alert-status--security', label: 'E-list' },\n { alertCodes: ['XELH'], classes: 'dps-alert-status dps-alert-status--security', label: 'E-list heightened' },\n { alertCodes: ['XER'], classes: 'dps-alert-status dps-alert-status--security', label: 'Escape risk' },\n {\n alertCodes: ['XRF'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Risk to females',\n },\n { alertCodes: ['XTACT'], classes: 'dps-alert-status dps-alert-status--security', label: 'TACT' },\n {\n alertCodes: ['XCO'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Corruptor',\n },\n {\n alertCodes: ['XCA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Chemical attacker',\n },\n {\n alertCodes: ['XCI'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Concerted indiscipline',\n },\n { alertCodes: ['XR'], classes: 'dps-alert-status dps-alert-status--security', label: 'Racist' },\n {\n alertCodes: ['RTP', 'RLG'],\n classes: 'dps-alert-status dps-alert-status--risk',\n label: 'Risk to LGBT',\n },\n {\n alertCodes: ['XHT'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Hostage taker',\n },\n {\n alertCodes: ['XCU'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Controlled unlock',\n },\n {\n alertCodes: ['XGANG'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Gang member',\n },\n { alertCodes: ['CSIP'], classes: 'dps-alert-status dps-alert-status--other', label: 'CSIP' },\n { alertCodes: ['F1'], classes: 'dps-alert-status dps-alert-status--ex-armed-forces', label: 'Veteran' },\n {\n alertCodes: ['LCE'],\n classes: 'dps-alert-status dps-alert-status--care-leaver',\n label: 'Care experienced',\n },\n {\n alertCodes: ['RNO121'],\n classes: 'dps-alert-status dps-alert-status--risk',\n label: 'No one-to-one',\n },\n { alertCodes: ['RCON'], classes: 'dps-alert-status dps-alert-status--risk', label: 'Conflict' },\n {\n alertCodes: ['RCDR'],\n classes: 'dps-alert-status dps-alert-status--quarantined',\n label: 'Quarantined',\n },\n {\n alertCodes: ['URCU'],\n classes: 'dps-alert-status dps-alert-status--reverse-cohorting-unit',\n label: 'Reverse Cohorting Unit',\n },\n {\n alertCodes: ['UPIU'],\n classes: 'dps-alert-status dps-alert-status--protective-isolation-unit',\n label: 'Protective Isolation Unit',\n },\n { alertCodes: ['USU'], classes: 'dps-alert-status dps-alert-status--shielding-unit', label: 'Shielding Unit' },\n {\n alertCodes: ['URS'],\n classes: 'dps-alert-status dps-alert-status--refusing-to-shield',\n label: 'Refusing to shield',\n },\n {\n alertCodes: ['RKS'],\n classes: 'dps-alert-status dps-alert-status--risk-to-known-adults',\n label: 'Risk to known adults',\n },\n { alertCodes: ['VIP'], classes: 'dps-alert-status dps-alert-status--isolated-prisoner', label: 'Isolated' },\n {\n alertCodes: ['PVN'],\n classes: 'dps-alert-status dps-alert-status--multicase dps-alert-status--visor',\n label: 'ViSOR',\n },\n {\n alertCodes: ['XCDO'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Involved in 2024 civil disorder',\n },\n {\n alertCodes: ['XVL'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Violent',\n },\n].sort((a, b) => a.label.localeCompare(b.label))\n\nfunction isAlertsServiceAlert(alert: Alert): alert is AlertsServiceAlert {\n return typeof alert.alertCode !== 'string'\n}\n\nexport default function getAlertFlagLabelsForAlerts(prisonerAlerts: Alert[]): AlertFlagLabel[] {\n return alertFlagLabels.reduce(\n (acc: AlertFlagLabel[], flag: { alertCodes: string[]; classes: string; label: string }) => {\n const alertIds = prisonerAlerts\n .filter(alert => {\n const alertsServiceAlert = isAlertsServiceAlert(alert)\n const alertIsActive = alertsServiceAlert ? alert.isActive : alert.active && !alert.expired\n const prisonerAlertCode = alertsServiceAlert ? alert.alertCode.code : alert.alertCode\n\n return alertIsActive && flag.alertCodes.includes(prisonerAlertCode)\n })\n .map(alert => (isAlertsServiceAlert(alert) ? (alert.alertUuid as string) : alert.alertCode))\n\n return alertIds.length ? [...acc, { ...flag, alertIds }] : acc\n },\n [] as AlertFlagLabel[],\n )\n}\n"],"names":[],"mappings":"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"}
|