@ministryofjustice/hmpps-digital-prison-reporting-frontend 9.2.0 → 9.2.2

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/all.bundle.js CHANGED
@@ -54,31 +54,6 @@ class DprClientClass {
54
54
  }
55
55
  }
56
56
 
57
- class CardGroup extends DprClientClass {
58
- static getModuleName() {
59
- return 'card-group';
60
- }
61
- initialise() {
62
- const cards = this.getElement().querySelectorAll('[data-click-navigate-to]');
63
- const wrapperClass = 'card-loading';
64
- cards.forEach(card => {
65
- card.addEventListener('click', () => {
66
- card.classList.add(wrapperClass);
67
- cards.forEach(c => {
68
- if (!c.classList.contains('card-loading')) {
69
- const disabledClass = 'card-disabled';
70
- c.classList.add(disabledClass);
71
- }
72
- });
73
- const url = card.dataset['clickNavigateTo'];
74
- if (url) {
75
- window.location.href = url;
76
- }
77
- });
78
- });
79
- }
80
- }
81
-
82
57
  var BookmarkAction;
83
58
  (function (BookmarkAction) {
84
59
  BookmarkAction["ADD"] = "add";
@@ -182,6 +157,31 @@ class BookmarkButton extends DprClientClass {
182
157
  }
183
158
  }
184
159
 
160
+ class CardGroup extends DprClientClass {
161
+ static getModuleName() {
162
+ return 'card-group';
163
+ }
164
+ initialise() {
165
+ const cards = this.getElement().querySelectorAll('[data-click-navigate-to]');
166
+ const wrapperClass = 'card-loading';
167
+ cards.forEach(card => {
168
+ card.addEventListener('click', () => {
169
+ card.classList.add(wrapperClass);
170
+ cards.forEach(c => {
171
+ if (!c.classList.contains('card-loading')) {
172
+ const disabledClass = 'card-disabled';
173
+ c.classList.add(disabledClass);
174
+ }
175
+ });
176
+ const url = card.dataset['clickNavigateTo'];
177
+ if (url) {
178
+ window.location.href = url;
179
+ }
180
+ });
181
+ });
182
+ }
183
+ }
184
+
185
185
  class dprTruncate extends DprClientClass {
186
186
  static getModuleName() {
187
187
  return 'dpr-truncate';
@@ -215,27 +215,6 @@ class dprTruncate extends DprClientClass {
215
215
  }
216
216
  }
217
217
 
218
- class Pagination {
219
- pageSizeSelect;
220
- static getModuleName() {
221
- return 'pagination';
222
- }
223
- initialise() {
224
- this.pageSizeSelect = document.getElementById('page-size-select');
225
- this.initPageSizeSelectEvent();
226
- }
227
- initPageSizeSelectEvent() {
228
- this.pageSizeSelect.addEventListener('change', () => {
229
- const { name, value } = this.pageSizeSelect;
230
- const queryParams = new URLSearchParams(window.location.search);
231
- queryParams.set(name, value);
232
- queryParams.set('selectedPage', '1');
233
- window.history.replaceState(null, '', `?${queryParams.toString()}`);
234
- window.location.reload();
235
- });
236
- }
237
- }
238
-
239
218
  class ReportActions extends DprClientClass {
240
219
  refreshButton;
241
220
  printButton;
@@ -318,8 +297,44 @@ class ReportActions extends DprClientClass {
318
297
  }
319
298
  }
320
299
 
321
- // @ts-nocheck
300
+ class DownloadMessage extends DprClientClass {
301
+ downloadMessage;
302
+ static getModuleName() {
303
+ return 'download-message';
304
+ }
305
+ initialise() {
306
+ this.downloadMessage = this.getElement();
307
+ if (window.location.href.includes('download-disabled')) {
308
+ this.downloadMessage.classList.remove('dpr-download-message--hidden');
309
+ }
310
+ }
311
+ }
312
+
313
+ class Pagination {
314
+ pageSizeSelect;
315
+ static getModuleName() {
316
+ return 'pagination';
317
+ }
318
+ initialise() {
319
+ this.pageSizeSelect = document.getElementById('page-size-select');
320
+ this.initPageSizeSelectEvent();
321
+ }
322
+ initPageSizeSelectEvent() {
323
+ this.pageSizeSelect.addEventListener('change', () => {
324
+ const { name, value } = this.pageSizeSelect;
325
+ const queryParams = new URLSearchParams(window.location.search);
326
+ queryParams.set(name, value);
327
+ queryParams.set('selectedPage', '1');
328
+ window.history.replaceState(null, '', `?${queryParams.toString()}`);
329
+ window.location.reload();
330
+ });
331
+ }
332
+ }
333
+
322
334
  class DataTable extends DprClientClass {
335
+ tableContainer = null;
336
+ table = null;
337
+ gradient = null;
323
338
  static getModuleName() {
324
339
  return 'data-table';
325
340
  }
@@ -336,9 +351,9 @@ class DataTable extends DprClientClass {
336
351
  window.addEventListener('resize', () => {
337
352
  this.checkOffsetWidths();
338
353
  });
339
- this.tableContainer.addEventListener('scroll', event => {
340
- const endOfScroll = this.table.offsetWidth;
341
- const currentScroll = event.target.offsetWidth + event.target.scrollLeft;
354
+ this.tableContainer?.addEventListener('scroll', event => {
355
+ const endOfScroll = this.table?.offsetWidth;
356
+ const currentScroll = event.target?.offsetWidth + event.target.scrollLeft;
342
357
  if (endOfScroll === currentScroll) {
343
358
  this.removeGradient();
344
359
  }
@@ -347,14 +362,21 @@ class DataTable extends DprClientClass {
347
362
  }
348
363
  });
349
364
  }
350
- removeGradient(gradient) {
351
- this.gradient.style.display = 'none';
365
+ removeGradient() {
366
+ if (this.gradient) {
367
+ this.gradient.style.display = 'none';
368
+ }
352
369
  }
353
- addGradient(gradient) {
354
- this.gradient.style.display = 'block';
370
+ addGradient() {
371
+ if (this.gradient) {
372
+ this.gradient.style.display = 'block';
373
+ }
355
374
  }
356
375
  checkOffsetWidths() {
357
- if (this.tableContainer.offsetWidth >= this.table.offsetWidth) {
376
+ if (!this.tableContainer || !this.table) {
377
+ return;
378
+ }
379
+ if (this.tableContainer?.offsetWidth >= this.table?.offsetWidth) {
358
380
  this.removeGradient();
359
381
  }
360
382
  else {
@@ -382,142 +404,6 @@ class DataTable extends DprClientClass {
382
404
  }
383
405
  }
384
406
 
385
- class DownloadMessage extends DprClientClass {
386
- downloadMessage;
387
- static getModuleName() {
388
- return 'download-message';
389
- }
390
- initialise() {
391
- this.downloadMessage = this.getElement();
392
- if (window.location.href.includes('download-disabled')) {
393
- this.downloadMessage.classList.remove('dpr-download-message--hidden');
394
- }
395
- }
396
- }
397
-
398
- class DprFiltersFormClass extends DprClientClass {
399
- form;
400
- static getModuleName() {
401
- return 'filters-form';
402
- }
403
- initialise() {
404
- if (!(this.element instanceof HTMLFormElement)) {
405
- throw new Error('DprFormQuerySync must be initialised on a form element');
406
- }
407
- this.form = this.element;
408
- this.bindInputEvents();
409
- }
410
- // ----------------------------------
411
- // Events
412
- // ----------------------------------
413
- bindInputEvents() {
414
- this.getAllInputs().forEach(input => {
415
- input.addEventListener('change', () => {
416
- const params = new URLSearchParams(window.location.search);
417
- this.updateQueryFromInput(input, params);
418
- this.markPreventDefault(params, input.name); // TODO: Check if this is needed ?
419
- this.resetPagination(params); // TODO: Check if this is needed ?
420
- this.replaceUrl(params);
421
- });
422
- });
423
- }
424
- // ----------------------------------
425
- // Query updates
426
- // ----------------------------------
427
- updateQueryFromInput(input, params) {
428
- const { name } = input;
429
- if (!name)
430
- return;
431
- if (input instanceof HTMLInputElement) {
432
- if (input.type === 'checkbox') {
433
- this.updateCheckbox(params, input);
434
- }
435
- else if (input.type === 'radio') {
436
- this.updateRadio(params, input);
437
- }
438
- else {
439
- this.updateSingle(params, input);
440
- }
441
- }
442
- else {
443
- this.updateSingle(params, input);
444
- }
445
- }
446
- updateCheckbox(params, input) {
447
- const values = params.getAll(input.name);
448
- // Remove all existing values for this key
449
- params.delete(input.name);
450
- // Re‑add all values except the unchecked one
451
- values
452
- .filter(value => value !== input.value || input.checked)
453
- .forEach(value => {
454
- params.append(input.name, value);
455
- });
456
- // Add the newly checked value
457
- if (input.checked && !values.includes(input.value)) {
458
- params.append(input.name, input.value);
459
- }
460
- }
461
- updateRadio(params, input) {
462
- if (!input.checked)
463
- return;
464
- const name = this.normaliseFilterName(input.name);
465
- params.set(name, input.value);
466
- }
467
- updateSingle(params, input) {
468
- const name = this.normaliseFilterName(input.name);
469
- let value = input.value;
470
- if (input instanceof HTMLInputElement && input.dataset['staticOptionNameValue']) {
471
- value = input.dataset['staticOptionNameValue'];
472
- }
473
- const isDateInput = input instanceof HTMLInputElement && input.classList.contains('moj-js-datepicker-input');
474
- if (isDateInput) {
475
- const formatted = dayjs(value, 'D/M/YYYY').format('YYYY-MM-DD');
476
- value = formatted !== 'Invalid Date' ? formatted : '';
477
- }
478
- const trimmed = value.trim();
479
- if (trimmed) {
480
- params.set(name, trimmed);
481
- }
482
- else {
483
- params.delete(name);
484
- }
485
- }
486
- // ----------------------------------
487
- // Behaviour flags
488
- // ----------------------------------
489
- /**
490
- * Signals to the server that defaults
491
- */
492
- markPreventDefault(params, changedName) {
493
- if (changedName !== 'columns') {
494
- params.set('preventDefault', 'true');
495
- }
496
- }
497
- /**
498
- * Reset pagination when filters change
499
- */
500
- resetPagination(params) {
501
- if (params.has('selectedPage')) {
502
- params.set('selectedPage', '1');
503
- }
504
- }
505
- replaceUrl(params) {
506
- const query = params.toString();
507
- const url = query ? `?${query}` : window.location.pathname;
508
- window.history.replaceState(null, '', url);
509
- }
510
- // ----------------------------------
511
- // DOM helpers
512
- // ----------------------------------
513
- getAllInputs() {
514
- return Array.from(this.form.querySelectorAll('input, select, textarea')).filter(el => el.name);
515
- }
516
- normaliseFilterName(name) {
517
- return name.startsWith('label.') ? name.replace(/^label\./, '') : name;
518
- }
519
- }
520
-
521
407
  dayjs.extend(customParse);
522
408
  /**
523
409
  * --------------------------------------------
@@ -822,12 +708,135 @@ class DprSelectedAsyncFilters extends DprClientClass {
822
708
  }
823
709
  }
824
710
 
825
- class DprAppliedFilters extends DprClientClass {
711
+ class DprFiltersFormClass extends DprClientClass {
712
+ form;
826
713
  static getModuleName() {
827
- return 'dpr-applied-filters';
714
+ return 'filters-form';
828
715
  }
829
716
  initialise() {
830
- this.bindRemoveEvents();
717
+ if (!(this.element instanceof HTMLFormElement)) {
718
+ throw new Error('DprFormQuerySync must be initialised on a form element');
719
+ }
720
+ this.form = this.element;
721
+ this.bindInputEvents();
722
+ }
723
+ // ----------------------------------
724
+ // Events
725
+ // ----------------------------------
726
+ bindInputEvents() {
727
+ this.getAllInputs().forEach(input => {
728
+ input.addEventListener('change', () => {
729
+ const params = new URLSearchParams(window.location.search);
730
+ this.updateQueryFromInput(input, params);
731
+ this.markPreventDefault(params, input.name); // TODO: Check if this is needed ?
732
+ this.resetPagination(params); // TODO: Check if this is needed ?
733
+ this.replaceUrl(params);
734
+ });
735
+ });
736
+ }
737
+ // ----------------------------------
738
+ // Query updates
739
+ // ----------------------------------
740
+ updateQueryFromInput(input, params) {
741
+ const { name } = input;
742
+ if (!name)
743
+ return;
744
+ if (input instanceof HTMLInputElement) {
745
+ if (input.type === 'checkbox') {
746
+ this.updateCheckbox(params, input);
747
+ }
748
+ else if (input.type === 'radio') {
749
+ this.updateRadio(params, input);
750
+ }
751
+ else {
752
+ this.updateSingle(params, input);
753
+ }
754
+ }
755
+ else {
756
+ this.updateSingle(params, input);
757
+ }
758
+ }
759
+ updateCheckbox(params, input) {
760
+ const values = params.getAll(input.name);
761
+ // Remove all existing values for this key
762
+ params.delete(input.name);
763
+ // Re‑add all values except the unchecked one
764
+ values
765
+ .filter(value => value !== input.value || input.checked)
766
+ .forEach(value => {
767
+ params.append(input.name, value);
768
+ });
769
+ // Add the newly checked value
770
+ if (input.checked && !values.includes(input.value)) {
771
+ params.append(input.name, input.value);
772
+ }
773
+ }
774
+ updateRadio(params, input) {
775
+ if (!input.checked)
776
+ return;
777
+ const name = this.normaliseFilterName(input.name);
778
+ params.set(name, input.value);
779
+ }
780
+ updateSingle(params, input) {
781
+ const name = this.normaliseFilterName(input.name);
782
+ let value = input.value;
783
+ if (input instanceof HTMLInputElement && input.dataset['staticOptionNameValue']) {
784
+ value = input.dataset['staticOptionNameValue'];
785
+ }
786
+ const isDateInput = input instanceof HTMLInputElement && input.classList.contains('moj-js-datepicker-input');
787
+ if (isDateInput) {
788
+ const formatted = dayjs(value, 'D/M/YYYY').format('YYYY-MM-DD');
789
+ value = formatted !== 'Invalid Date' ? formatted : '';
790
+ }
791
+ const trimmed = value.trim();
792
+ if (trimmed) {
793
+ params.set(name, trimmed);
794
+ }
795
+ else {
796
+ params.delete(name);
797
+ }
798
+ }
799
+ // ----------------------------------
800
+ // Behaviour flags
801
+ // ----------------------------------
802
+ /**
803
+ * Signals to the server that defaults
804
+ */
805
+ markPreventDefault(params, changedName) {
806
+ if (changedName !== 'columns') {
807
+ params.set('preventDefault', 'true');
808
+ }
809
+ }
810
+ /**
811
+ * Reset pagination when filters change
812
+ */
813
+ resetPagination(params) {
814
+ if (params.has('selectedPage')) {
815
+ params.set('selectedPage', '1');
816
+ }
817
+ }
818
+ replaceUrl(params) {
819
+ const query = params.toString();
820
+ const url = query ? `?${query}` : window.location.pathname;
821
+ window.history.replaceState(null, '', url);
822
+ }
823
+ // ----------------------------------
824
+ // DOM helpers
825
+ // ----------------------------------
826
+ getAllInputs() {
827
+ return Array.from(this.form.querySelectorAll('input, select, textarea')).filter(el => el.name);
828
+ }
829
+ normaliseFilterName(name) {
830
+ return name.startsWith('label.') ? name.replace(/^label\./, '') : name;
831
+ }
832
+ }
833
+
834
+ class DprAppliedFilters extends DprClientClass {
835
+ static getModuleName() {
836
+ return 'dpr-applied-filters';
837
+ }
838
+ initialise() {
839
+ this.bindRemoveEvents();
831
840
  }
832
841
  // ----------------------------------
833
842
  // Event binding
@@ -1033,9 +1042,9 @@ class DprReportStatus extends PollingClientClass {
1033
1042
  }
1034
1043
  }
1035
1044
 
1036
- // @ts-nocheck
1037
1045
  /* eslint-disable class-methods-use-this */
1038
1046
  class DprSyncLoading extends DprClientClass {
1047
+ form = null;
1039
1048
  static getModuleName() {
1040
1049
  return 'sync-loading';
1041
1050
  }
@@ -1045,96 +1054,345 @@ class DprSyncLoading extends DprClientClass {
1045
1054
  this.load();
1046
1055
  }
1047
1056
  async load() {
1048
- this.form.submit();
1057
+ this.form?.submit();
1049
1058
  }
1050
1059
  }
1051
1060
 
1052
- // @ts-nocheck
1053
- class DateInput extends DprClientClass {
1061
+ class AutoCompleteMulti extends DprClientClass {
1062
+ filterId;
1063
+ searchInput;
1064
+ searchInputValue;
1065
+ multiselectOptions;
1054
1066
  static getModuleName() {
1055
- return 'date-input';
1067
+ return 'autocomplete-multiselect-input';
1056
1068
  }
1057
1069
  initialise() {
1058
- const element = this.getElement();
1059
- this.dateInput = element.querySelector(`input.moj-js-datepicker-input`);
1060
- this.setToValueTriggers = document.querySelectorAll(`[data-set-min-max-trigger='true']`);
1061
- this.required = this.getElement().getAttribute('data-required');
1062
- this.displayName = this.getElement().getAttribute('data-display-name');
1063
- this.pattern = this.getElement().getAttribute('data-pattern');
1064
- this.patternHint = this.getElement().getAttribute('data-pattern-hint');
1065
- this.min = this.getElement().getAttribute('data-min');
1066
- this.max = this.getElement().getAttribute('data-max');
1067
- this.setValidationOnInputEl();
1068
- this.setMinMaxEventListener();
1069
- this.setToMinMax();
1070
- this.setToValue();
1070
+ this.element = this.getElement();
1071
+ this.filterId = this.element.getAttribute('data-filter-id');
1072
+ // Search input
1073
+ this.searchInput = document.getElementById(`search.${this.filterId}`);
1074
+ this.multiselectOptions = Array.from(this.element.querySelectorAll('.govuk-checkboxes__input'));
1075
+ if (!this.searchInput || !this.multiselectOptions)
1076
+ return;
1077
+ this.initialiseCheckboxes();
1078
+ this.initSearchInputAction();
1071
1079
  }
1072
- setValidationOnInputEl() {
1073
- if (this.required && this.required === 'true') {
1074
- this.dateInput.setAttribute('required', true);
1075
- }
1076
- if (this.min)
1077
- this.dateInput.setAttribute('min', this.min);
1078
- if (this.max)
1079
- this.dateInput.setAttribute('max', this.max);
1080
- this.dateInput.setAttribute('display-name', this.displayName);
1081
- this.dateInput.setAttribute('pattern', this.pattern);
1082
- this.dateInput.setAttribute('pattern-hint', this.patternHint);
1080
+ initialiseCheckboxes() {
1081
+ this.multiselectOptions.forEach(input => {
1082
+ const wrapper = input.closest('.govuk-checkboxes__item');
1083
+ if (!wrapper)
1084
+ return;
1085
+ wrapper.classList.toggle('dpr-form--hidden', !input.checked);
1086
+ });
1083
1087
  }
1084
- setMinMaxEventListener() {
1085
- this.dateInput.addEventListener('blur', () => {
1086
- this.setToMinMax();
1088
+ initSearchInputAction() {
1089
+ if (!this.searchInput)
1090
+ return;
1091
+ const input = this.searchInput;
1092
+ this.searchInput.addEventListener('keyup', _event => {
1093
+ this.searchInputValue = input.value;
1094
+ this.updateCheckboxes();
1087
1095
  });
1088
1096
  }
1089
- setToMinMax() {
1090
- if (this.dateInput.value) {
1091
- const dateValue = new Date(this.dateInput.value);
1092
- if (this.min) {
1093
- const minDate = new Date(this.min);
1094
- if (dateValue < minDate) {
1095
- this.dateInput.value = dayjs(this.min).format('DD/MM/YYYY');
1096
- }
1097
+ updateCheckboxes() {
1098
+ const query = this.searchInputValue.toLowerCase().trim();
1099
+ const minLength = 3;
1100
+ this.multiselectOptions.forEach(input => {
1101
+ const wrapper = input.closest('.govuk-checkboxes__item');
1102
+ if (!wrapper)
1103
+ return;
1104
+ // Always show selected items
1105
+ if (input.checked) {
1106
+ wrapper.classList.remove('dpr-form--hidden');
1107
+ return;
1097
1108
  }
1098
- if (this.max) {
1099
- const maxDate = new Date(this.max);
1100
- if (dateValue > maxDate) {
1101
- this.dateInput.value = dayjs(this.max).format('DD/MM/YYYY');
1102
- }
1109
+ // Below threshold - hide unselected
1110
+ if (query.length < minLength) {
1111
+ wrapper.classList.add('dpr-form--hidden');
1112
+ return;
1103
1113
  }
1104
- }
1105
- const changeEvent = new Event('change');
1106
- this.dateInput.dispatchEvent(changeEvent);
1114
+ const labelText = input.labels?.[0]?.innerText.toLowerCase() ?? '';
1115
+ const matches = labelText.includes(query);
1116
+ wrapper.classList.toggle('dpr-form--hidden', !matches);
1117
+ });
1107
1118
  }
1108
- setToValue() {
1109
- this.setToValueTriggers.forEach(set => {
1110
- set.addEventListener('click', e => {
1119
+ }
1120
+
1121
+ /* eslint-disable class-methods-use-this */
1122
+ class Autocomplete extends DprClientClass {
1123
+ listItemsSelector;
1124
+ listParentSelector;
1125
+ static getModuleName() {
1126
+ return 'autocomplete-text-input';
1127
+ }
1128
+ constructor(element) {
1129
+ super(element);
1130
+ const listId = this.getTextInput()?.getAttribute('aria-owns');
1131
+ this.listItemsSelector = `#${listId} li`;
1132
+ this.listParentSelector = `#${listId} ul`;
1133
+ }
1134
+ initialise() {
1135
+ const textInput = this.getTextInput();
1136
+ textInput?.addEventListener('keyup', event => {
1137
+ this.onTextInput(event, textInput);
1138
+ });
1139
+ textInput?.addEventListener('keypress', e => {
1140
+ if (e.key === 'Enter') {
1141
+ e.stopPropagation();
1111
1142
  e.preventDefault();
1112
- const value = e.target.getAttribute('data-set-min-max-value');
1113
- const inputId = e.target.getAttribute('data-set-to-input');
1114
- const input = document.getElementById(inputId);
1115
- input.value = value;
1116
- const changeEvent = new Event('change');
1117
- input.dispatchEvent(changeEvent);
1143
+ }
1144
+ });
1145
+ textInput?.addEventListener('input', () => {
1146
+ if (textInput.value !== '') {
1147
+ return;
1148
+ }
1149
+ const hiddenInput = this.getHiddenInput();
1150
+ if (hiddenInput) {
1151
+ hiddenInput.value = '';
1152
+ hiddenInput.disabled = true;
1153
+ }
1154
+ delete textInput.dataset['staticOptionNameValue'];
1155
+ textInput.dispatchEvent(new Event('change', { bubbles: true }));
1156
+ });
1157
+ this.getElement()
1158
+ .querySelectorAll('.autocomplete-text-input-list-button')
1159
+ .forEach(button => {
1160
+ button.addEventListener('mousedown', event => {
1161
+ this.onOptionClick(event, textInput, this.getElement());
1118
1162
  });
1119
1163
  });
1164
+ this.initialiseDefaultValue(textInput);
1165
+ }
1166
+ initialiseDefaultValue(textInput) {
1167
+ const hiddenInput = this.getHiddenInput();
1168
+ if (hiddenInput?.value) {
1169
+ hiddenInput.disabled = false;
1170
+ return;
1171
+ }
1172
+ if (textInput) {
1173
+ textInput.value = '';
1174
+ }
1175
+ delete textInput?.dataset['staticOptionNameValue'];
1176
+ if (hiddenInput) {
1177
+ hiddenInput.value = '';
1178
+ hiddenInput.disabled = true;
1179
+ }
1180
+ }
1181
+ getTextInput() {
1182
+ return this.getElement().querySelector('.autocomplete-text-input-box');
1183
+ }
1184
+ onTextInput(event, textInput) {
1185
+ const minLength = Number(textInput.dataset['minimumLength']);
1186
+ const { resourceEndpoint } = textInput.dataset;
1187
+ const searchValue = event.target.value.toLowerCase();
1188
+ if (resourceEndpoint) {
1189
+ if (searchValue.length >= minLength) {
1190
+ this.addItem(this.clearListAndRecreateTemplate(), '<i>Searching...</i>');
1191
+ this.populateOptionsDynamically(resourceEndpoint, searchValue, textInput, () => this.clearListAndRecreateTemplate());
1192
+ }
1193
+ else {
1194
+ this.clearListAndRecreateTemplate();
1195
+ }
1196
+ }
1197
+ else {
1198
+ this.getElement()
1199
+ .querySelectorAll(this.listItemsSelector)
1200
+ .forEach(item => {
1201
+ if (searchValue.length >= minLength &&
1202
+ this.isMatchingStaticOptionNameOrDisplayPrefix(this.getInputListButton(item), searchValue, item)) {
1203
+ item.classList.remove('autocomplete-text-input-item-hide');
1204
+ }
1205
+ else {
1206
+ item.classList.add('autocomplete-text-input-item-hide');
1207
+ }
1208
+ });
1209
+ }
1210
+ if (searchValue.length === 0) {
1211
+ const changeEvent = new Event('change');
1212
+ textInput.dispatchEvent(changeEvent);
1213
+ }
1214
+ }
1215
+ getInputListButton(item) {
1216
+ return item.querySelector('.autocomplete-text-input-list-button');
1217
+ }
1218
+ isMatchingStaticOptionNameOrDisplayPrefix(inputListButton, searchValue, item) {
1219
+ return (this.isStaticOptionsNamePrefix(inputListButton?.dataset['staticOptionNameValue'], searchValue) ||
1220
+ item.innerText.trim().toLowerCase().startsWith(searchValue));
1221
+ }
1222
+ isStaticOptionsNamePrefix(staticOptionNameValue, searchValue) {
1223
+ return staticOptionNameValue && staticOptionNameValue.trim().toLowerCase().startsWith(searchValue);
1224
+ }
1225
+ async populateOptionsDynamically(resourceEndpoint, searchValue, textInput, templateProvider) {
1226
+ try {
1227
+ const response = await fetch(resourceEndpoint.replace('{prefix}', encodeURI(searchValue)));
1228
+ const results = await response.json();
1229
+ if (searchValue === textInput?.value.toLowerCase()) {
1230
+ const template = templateProvider();
1231
+ results.forEach((result) => {
1232
+ this.addItem(template, result, event => {
1233
+ this.onOptionClick(event, textInput, this.getElement());
1234
+ });
1235
+ });
1236
+ }
1237
+ }
1238
+ catch (error) {
1239
+ this.addItem(templateProvider(), `Failed to retrieve results: ${error}`);
1240
+ }
1241
+ }
1242
+ onOptionClick(event, textInput, topLevelElement) {
1243
+ event.preventDefault();
1244
+ const button = event.currentTarget?.closest('button');
1245
+ const hiddenInput = this.getHiddenInput();
1246
+ const displayValue = button?.innerText.trim();
1247
+ const actualValue = button?.dataset['staticOptionNameValue'] || '';
1248
+ // UI Display Value
1249
+ if (textInput) {
1250
+ this.setValue(textInput, displayValue);
1251
+ textInput.dataset['staticOptionNameValue'] = actualValue;
1252
+ }
1253
+ // submission value
1254
+ if (hiddenInput) {
1255
+ hiddenInput.value = actualValue;
1256
+ hiddenInput.disabled = false;
1257
+ }
1258
+ topLevelElement.querySelectorAll('li').forEach(item => {
1259
+ item.classList.add('autocomplete-text-input-item-hide');
1260
+ });
1261
+ }
1262
+ setValue(textInput, displayValue) {
1263
+ if (displayValue) {
1264
+ textInput.value = displayValue;
1265
+ }
1266
+ textInput.focus();
1267
+ textInput.dispatchEvent(new Event('change', { bubbles: true }));
1268
+ }
1269
+ addItem(template, content, clickEvent) {
1270
+ const item = template?.cloneNode(true);
1271
+ const button = item?.querySelector('button');
1272
+ if (button) {
1273
+ button.innerHTML = content;
1274
+ }
1275
+ item.classList.remove('autocomplete-text-input-item-hide');
1276
+ this.getElement().querySelector(this.listParentSelector)?.appendChild(item);
1277
+ if (clickEvent) {
1278
+ item.addEventListener('mousedown', (event) => {
1279
+ clickEvent(event);
1280
+ });
1281
+ }
1282
+ }
1283
+ clearListAndRecreateTemplate() {
1284
+ const template = this.getElement()
1285
+ .querySelector(this.listItemsSelector)
1286
+ ?.cloneNode(true);
1287
+ template?.classList.add('autocomplete-text-input-item-hide');
1288
+ this.getElement()
1289
+ .querySelectorAll(this.listItemsSelector)
1290
+ .forEach(e => e.remove());
1291
+ if (template) {
1292
+ this.getElement().querySelector(this.listParentSelector)?.append(template);
1293
+ }
1294
+ return template;
1295
+ }
1296
+ getHiddenInput() {
1297
+ return this.getElement().querySelector('[data-autocomplete-hidden]');
1120
1298
  }
1121
1299
  }
1122
1300
 
1123
- var isBetween$2 = {exports: {}};
1124
-
1125
- var isBetween$1 = isBetween$2.exports;
1126
-
1127
- var hasRequiredIsBetween;
1128
-
1129
- function requireIsBetween () {
1130
- if (hasRequiredIsBetween) return isBetween$2.exports;
1131
- hasRequiredIsBetween = 1;
1132
- (function (module, exports) {
1133
- !function(e,i){module.exports=i();}(isBetween$1,(function(){return function(e,i,t){i.prototype.isBetween=function(e,i,s,f){var n=t(e),o=t(i),r="("===(f=f||"()")[0],u=")"===f[1];return (r?this.isAfter(n,s):!this.isBefore(n,s))&&(u?this.isBefore(o,s):!this.isAfter(o,s))||(r?this.isBefore(n,s):!this.isAfter(n,s))&&(u?this.isAfter(o,s):!this.isBefore(o,s))};}}));
1134
- } (isBetween$2));
1135
- return isBetween$2.exports;
1136
- }
1137
-
1301
+ class DateInput extends DprClientClass {
1302
+ dateInput = null;
1303
+ required = null;
1304
+ displayName = null;
1305
+ pattern = null;
1306
+ patternHint = null;
1307
+ min = null;
1308
+ max = null;
1309
+ setToValueTriggers = null;
1310
+ static getModuleName() {
1311
+ return 'date-input';
1312
+ }
1313
+ initialise() {
1314
+ const element = this.getElement();
1315
+ this.dateInput = element.querySelector(`input.moj-js-datepicker-input`);
1316
+ this.setToValueTriggers = document.querySelectorAll(`[data-set-min-max-trigger='true']`);
1317
+ this.required = this.getElement().getAttribute('data-required');
1318
+ this.displayName = this.getElement().getAttribute('data-display-name');
1319
+ this.pattern = this.getElement().getAttribute('data-pattern');
1320
+ this.patternHint = this.getElement().getAttribute('data-pattern-hint');
1321
+ this.min = this.getElement().getAttribute('data-min');
1322
+ this.max = this.getElement().getAttribute('data-max');
1323
+ this.setValidationOnInputEl();
1324
+ this.setMinMaxEventListener();
1325
+ this.setToMinMax();
1326
+ this.setToValue();
1327
+ }
1328
+ setValidationOnInputEl() {
1329
+ if (this.required && this.required === 'true') {
1330
+ this.dateInput?.setAttribute('required', 'true');
1331
+ }
1332
+ if (this.min)
1333
+ this.dateInput?.setAttribute('min', this.min);
1334
+ if (this.max)
1335
+ this.dateInput?.setAttribute('max', this.max);
1336
+ this.displayName && this.dateInput?.setAttribute('display-name', this.displayName);
1337
+ this.pattern && this.dateInput?.setAttribute('pattern', this.pattern);
1338
+ this.patternHint && this.dateInput?.setAttribute('pattern-hint', this.patternHint);
1339
+ }
1340
+ setMinMaxEventListener() {
1341
+ this.dateInput?.addEventListener('blur', () => {
1342
+ this.setToMinMax();
1343
+ });
1344
+ }
1345
+ setToMinMax() {
1346
+ if (this.dateInput?.value) {
1347
+ const dateValue = new Date(this.dateInput.value);
1348
+ if (this.min) {
1349
+ const minDate = new Date(this.min);
1350
+ if (dateValue < minDate) {
1351
+ this.dateInput.value = dayjs(this.min).format('DD/MM/YYYY');
1352
+ }
1353
+ }
1354
+ if (this.max) {
1355
+ const maxDate = new Date(this.max);
1356
+ if (dateValue > maxDate) {
1357
+ this.dateInput.value = dayjs(this.max).format('DD/MM/YYYY');
1358
+ }
1359
+ }
1360
+ }
1361
+ const changeEvent = new Event('change');
1362
+ this.dateInput?.dispatchEvent(changeEvent);
1363
+ }
1364
+ setToValue() {
1365
+ this.setToValueTriggers?.forEach(set => {
1366
+ set.addEventListener('click', e => {
1367
+ e.preventDefault();
1368
+ const value = e.target?.getAttribute('data-set-min-max-value');
1369
+ const inputId = e.target?.getAttribute('data-set-to-input') || '';
1370
+ const input = document.getElementById(inputId);
1371
+ if (input && value) {
1372
+ input.value = value;
1373
+ const changeEvent = new Event('change');
1374
+ input?.dispatchEvent(changeEvent);
1375
+ }
1376
+ });
1377
+ });
1378
+ }
1379
+ }
1380
+
1381
+ var isBetween$2 = {exports: {}};
1382
+
1383
+ var isBetween$1 = isBetween$2.exports;
1384
+
1385
+ var hasRequiredIsBetween;
1386
+
1387
+ function requireIsBetween () {
1388
+ if (hasRequiredIsBetween) return isBetween$2.exports;
1389
+ hasRequiredIsBetween = 1;
1390
+ (function (module, exports) {
1391
+ !function(e,i){module.exports=i();}(isBetween$1,(function(){return function(e,i,t){i.prototype.isBetween=function(e,i,s,f){var n=t(e),o=t(i),r="("===(f=f||"()")[0],u=")"===f[1];return (r?this.isAfter(n,s):!this.isBefore(n,s))&&(u?this.isBefore(o,s):!this.isAfter(o,s))||(r?this.isBefore(n,s):!this.isAfter(n,s))&&(u?this.isAfter(o,s):!this.isBefore(o,s))};}}));
1392
+ } (isBetween$2));
1393
+ return isBetween$2.exports;
1394
+ }
1395
+
1138
1396
  var isBetweenExports = requireIsBetween();
1139
1397
  var isBetween = /*@__PURE__*/getDefaultExportFromCjs(isBetweenExports);
1140
1398
 
@@ -1289,181 +1547,18 @@ class DateRangeInput extends DprClientClass {
1289
1547
  }
1290
1548
  }
1291
1549
 
1292
- // @ts-nocheck
1293
- /* eslint-disable class-methods-use-this */
1294
- class Autocomplete extends DprClientClass {
1295
- static getModuleName() {
1296
- return 'autocomplete-text-input';
1297
- }
1298
- constructor(element) {
1299
- super(element);
1300
- const listId = this.getTextInput().getAttribute('aria-owns');
1301
- this.listItemsSelector = `#${listId} li`;
1302
- this.listParentSelector = `#${listId} ul`;
1303
- }
1304
- initialise() {
1305
- const textInput = this.getTextInput();
1306
- textInput.addEventListener('keyup', event => {
1307
- this.onTextInput(event, textInput);
1308
- });
1309
- textInput.addEventListener('keypress', e => {
1310
- if (e.key === 'Enter') {
1311
- e.stopPropagation();
1312
- e.preventDefault();
1313
- }
1314
- });
1315
- textInput.addEventListener('input', () => {
1316
- if (textInput.value !== '') {
1317
- return;
1318
- }
1319
- const hiddenInput = this.getHiddenInput();
1320
- if (hiddenInput) {
1321
- hiddenInput.value = '';
1322
- hiddenInput.disabled = true;
1323
- }
1324
- delete textInput.dataset.staticOptionNameValue;
1325
- textInput.dispatchEvent(new Event('change', { bubbles: true }));
1326
- });
1327
- this.getElement()
1328
- .querySelectorAll('.autocomplete-text-input-list-button')
1329
- .forEach(button => {
1330
- button.addEventListener('mousedown', event => {
1331
- this.onOptionClick(event, textInput, this.getElement());
1332
- });
1333
- });
1334
- this.initialiseDefaultValue(textInput);
1335
- }
1336
- initialiseDefaultValue(textInput) {
1337
- const hiddenInput = this.getHiddenInput();
1338
- if (hiddenInput?.value) {
1339
- hiddenInput.disabled = false;
1340
- return;
1341
- }
1342
- textInput.value = '';
1343
- delete textInput.dataset.staticOptionNameValue;
1344
- if (hiddenInput) {
1345
- hiddenInput.value = '';
1346
- hiddenInput.disabled = true;
1347
- }
1348
- }
1349
- getTextInput() {
1350
- return this.getElement().querySelector('.autocomplete-text-input-box');
1351
- }
1352
- onTextInput(event, textInput) {
1353
- const minLength = Number(textInput.dataset.minimumLength);
1354
- const { resourceEndpoint } = textInput.dataset;
1355
- const searchValue = event.target.value.toLowerCase();
1356
- if (resourceEndpoint) {
1357
- if (searchValue.length >= minLength) {
1358
- this.addItem(this.clearListAndRecreateTemplate(), '<i>Searching...</i>');
1359
- this.populateOptionsDynamically(resourceEndpoint, searchValue, textInput, () => this.clearListAndRecreateTemplate());
1360
- }
1361
- else {
1362
- this.clearListAndRecreateTemplate();
1363
- }
1364
- }
1365
- else {
1366
- this.getElement()
1367
- .querySelectorAll(this.listItemsSelector)
1368
- .forEach(item => {
1369
- if (searchValue.length >= minLength &&
1370
- this.isMatchingStaticOptionNameOrDisplayPrefix(this.getInputListButton(item), searchValue, item)) {
1371
- item.classList.remove('autocomplete-text-input-item-hide');
1372
- }
1373
- else {
1374
- item.classList.add('autocomplete-text-input-item-hide');
1375
- }
1376
- });
1377
- }
1378
- if (searchValue.length === 0) {
1379
- const changeEvent = new Event('change');
1380
- textInput.dispatchEvent(changeEvent);
1381
- }
1382
- }
1383
- getInputListButton(item) {
1384
- return item.querySelector('.autocomplete-text-input-list-button');
1385
- }
1386
- isMatchingStaticOptionNameOrDisplayPrefix(inputListButton, searchValue, item) {
1387
- return (this.isStaticOptionsNamePrefix(inputListButton.dataset.staticOptionNameValue, searchValue) ||
1388
- item.innerText.trim().toLowerCase().startsWith(searchValue));
1389
- }
1390
- isStaticOptionsNamePrefix(staticOptionNameValue, searchValue) {
1391
- return staticOptionNameValue && staticOptionNameValue.trim().toLowerCase().startsWith(searchValue);
1392
- }
1393
- async populateOptionsDynamically(resourceEndpoint, searchValue, textInput, templateProvider) {
1394
- try {
1395
- const response = await fetch(resourceEndpoint.replace('{prefix}', encodeURI(searchValue)));
1396
- const results = await response.json();
1397
- if (searchValue === textInput.value.toLowerCase()) {
1398
- const template = templateProvider();
1399
- results.forEach(r => {
1400
- this.addItem(template, r, event => {
1401
- this.onOptionClick(event, textInput, this.getElement());
1402
- });
1403
- });
1404
- }
1405
- }
1406
- catch (error) {
1407
- this.addItem(templateProvider(), `Failed to retrieve results: ${error}`);
1408
- }
1409
- }
1410
- onOptionClick(event, textInput, topLevelElement) {
1411
- event.preventDefault();
1412
- const button = event.currentTarget.closest('button');
1413
- const hiddenInput = this.getHiddenInput();
1414
- const displayValue = button.innerText.trim();
1415
- const actualValue = button.dataset.staticOptionNameValue || '';
1416
- // UI Display Value
1417
- textInput.value = displayValue;
1418
- // submission value
1419
- if (hiddenInput) {
1420
- hiddenInput.value = actualValue;
1421
- hiddenInput.disabled = false;
1422
- }
1423
- textInput.dataset.staticOptionNameValue = actualValue;
1424
- topLevelElement.querySelectorAll('li').forEach(item => {
1425
- item.classList.add('autocomplete-text-input-item-hide');
1426
- });
1427
- textInput.focus();
1428
- textInput.dispatchEvent(new Event('change', { bubbles: true }));
1429
- }
1430
- setValue(textInput, displayValue) {
1431
- textInput.value = displayValue;
1432
- textInput.focus();
1433
- textInput.dispatchEvent(new Event('change', { bubbles: true }));
1434
- }
1435
- addItem(template, content, clickEvent) {
1436
- const item = template.cloneNode(true);
1437
- item.querySelector('button').innerHTML = content;
1438
- item.classList.remove('autocomplete-text-input-item-hide');
1439
- this.getElement().querySelector(this.listParentSelector).appendChild(item);
1440
- if (clickEvent) {
1441
- item.addEventListener('mousedown', event => {
1442
- clickEvent(event);
1443
- });
1444
- }
1445
- }
1446
- clearListAndRecreateTemplate() {
1447
- const template = this.getElement().querySelector(this.listItemsSelector).cloneNode(true);
1448
- template.classList.add('autocomplete-text-input-item-hide');
1449
- this.getElement()
1450
- .querySelectorAll(this.listItemsSelector)
1451
- .forEach(e => e.remove());
1452
- this.getElement().querySelector(this.listParentSelector).append(template);
1453
- return template;
1454
- }
1455
- getHiddenInput() {
1456
- return this.getElement().querySelector('[data-autocomplete-hidden]');
1457
- }
1458
- }
1459
-
1460
- // @ts-nocheck
1461
1550
  class GranularDateRange extends DprClientClass {
1462
- quickFiltersInput;
1463
- granularityInput;
1464
- startInput;
1465
- endInput;
1466
- currentQuickFilterValue;
1551
+ filter = null;
1552
+ quickFiltersInput = null;
1553
+ granularityInput = null;
1554
+ startInput = null;
1555
+ endInput = null;
1556
+ currentQuickFilterValue = '';
1557
+ fieldName = null;
1558
+ idPrefix = '';
1559
+ currentStartInputValue = '';
1560
+ currentEndInputValue = '';
1561
+ currentGranularityValue = '';
1467
1562
  static getModuleName() {
1468
1563
  return 'granular-date-range-input';
1469
1564
  }
@@ -1475,15 +1570,15 @@ class GranularDateRange extends DprClientClass {
1475
1570
  this.granularityInput = this.filter.querySelector(`select[name='${this.idPrefix}.granularity']`);
1476
1571
  this.startInput = this.filter.querySelector(`input[name='${this.idPrefix}.start']`);
1477
1572
  this.endInput = this.filter.querySelector(`input[name='${this.idPrefix}.end']`);
1478
- this.currentStartInputValue = this.startInput.value;
1479
- this.currentEndInputValue = this.endInput.value;
1480
- this.currentQuickFilterValue = this.quickFiltersInput.value;
1481
- this.currentGranularityValue = this.granularityInput.value;
1573
+ this.currentStartInputValue = this.startInput?.value || '';
1574
+ this.currentEndInputValue = this.endInput?.value || '';
1575
+ this.currentQuickFilterValue = this.quickFiltersInput?.value || '';
1576
+ this.currentGranularityValue = this.granularityInput?.value || '';
1482
1577
  this.initChangeEvents();
1483
1578
  }
1484
1579
  initChangeEvents() {
1485
1580
  [this.granularityInput, this.quickFiltersInput, this.startInput, this.endInput].forEach(el => {
1486
- el.addEventListener('change', event => {
1581
+ el?.addEventListener('change', event => {
1487
1582
  this.resolveStateChange(event);
1488
1583
  });
1489
1584
  });
@@ -1492,22 +1587,22 @@ class GranularDateRange extends DprClientClass {
1492
1587
  const target = event.target;
1493
1588
  const value = target.value;
1494
1589
  switch (target.id) {
1495
- case this.quickFiltersInput.id: {
1590
+ case this.quickFiltersInput?.id: {
1496
1591
  this.currentQuickFilterValue = value;
1497
1592
  const { granularity, startDate, endDate } = this.calculateStartEndGranularity(value);
1498
- this.granularityInput.value = granularity;
1499
- this.startInput.value = startDate.format('DD/MM/YYYY').toString();
1500
- this.endInput.value = endDate.format('DD/MM/YYYY').toString();
1593
+ this.granularityInput && (this.granularityInput.value = granularity);
1594
+ this.startInput && (this.startInput.value = startDate.format('DD/MM/YYYY').toString());
1595
+ this.endInput && (this.endInput.value = endDate.format('DD/MM/YYYY').toString());
1501
1596
  break;
1502
1597
  }
1503
- case this.granularityInput.id: {
1598
+ case this.granularityInput?.id: {
1504
1599
  if (this.shouldResetQuickFilters(event)) {
1505
1600
  this.resetQuickFiltersToNone();
1506
1601
  }
1507
1602
  break;
1508
1603
  }
1509
- case this.startInput.id:
1510
- case this.endInput.id: {
1604
+ case this.startInput?.id:
1605
+ case this.endInput?.id: {
1511
1606
  this.resetQuickFiltersToNone();
1512
1607
  break;
1513
1608
  }
@@ -1519,14 +1614,14 @@ class GranularDateRange extends DprClientClass {
1519
1614
  this.updateQueryParams();
1520
1615
  }
1521
1616
  resetQuickFiltersToNone() {
1522
- this.quickFiltersInput.value = 'none';
1617
+ this.quickFiltersInput && (this.quickFiltersInput.value = 'none');
1523
1618
  }
1524
1619
  updateQueryParams() {
1525
1620
  const queryParams = new URLSearchParams(window.location.search);
1526
- queryParams.set(this.granularityInput.id, this.granularityInput.value);
1527
- queryParams.set(this.quickFiltersInput.id, this.quickFiltersInput.value);
1528
- queryParams.set(this.startInput.id, this.startInput.value);
1529
- queryParams.set(this.endInput.id, this.endInput.value);
1621
+ this.granularityInput && queryParams.set(this.granularityInput.id, this.granularityInput.value);
1622
+ this.quickFiltersInput && queryParams.set(this.quickFiltersInput.id, this.quickFiltersInput.value);
1623
+ this.startInput && queryParams.set(this.startInput.id, this.startInput.value);
1624
+ this.endInput && queryParams.set(this.endInput.id, this.endInput.value);
1530
1625
  window.history.replaceState(null, '', `?${queryParams.toString()}`);
1531
1626
  }
1532
1627
  shouldResetQuickFilters(e) {
@@ -1540,15 +1635,15 @@ class GranularDateRange extends DprClientClass {
1540
1635
  return false;
1541
1636
  }
1542
1637
  calculateStartEndGranularity(quickFilterValue) {
1543
- let startDate = dayjs(this.startInput.value);
1544
- let endDate = dayjs(this.endInput.value);
1545
- let granularity = this.granularityInput.value;
1638
+ let startDate = dayjs(this.startInput?.value);
1639
+ let endDate = dayjs(this.endInput?.value);
1640
+ let granularity = this.granularityInput?.value;
1546
1641
  switch (quickFilterValue) {
1547
1642
  // This case only happens if quick filter is _already_ none and someone changes granularity
1548
1643
  case 'none':
1549
1644
  endDate = dayjs();
1550
1645
  startDate = dayjs();
1551
- granularity = this.granularityInput.value;
1646
+ granularity = this.granularityInput?.value;
1552
1647
  break;
1553
1648
  case 'today':
1554
1649
  endDate = dayjs();
@@ -1669,7 +1764,7 @@ class GranularDateRange extends DprClientClass {
1669
1764
  return {
1670
1765
  startDate,
1671
1766
  endDate,
1672
- granularity,
1767
+ granularity: String(granularity),
1673
1768
  };
1674
1769
  }
1675
1770
  }
@@ -1743,76 +1838,16 @@ class MultiselectInput extends DprClientClass {
1743
1838
  /**
1744
1839
  * Removes classes to relevant element to hide all select items
1745
1840
  *
1746
- * @param {Event} e
1747
- * @memberof MultiselectInput
1748
- */
1749
- removeFullWidthClasses(e) {
1750
- e.preventDefault();
1751
- this.element.classList.remove('multiselect-container__full-width');
1752
- this.filtersContainer?.classList.remove('dpr-filter-item__span-3');
1753
- // Update button visability
1754
- this.fullListLink?.classList.remove('dpr-multiselect-action--hide');
1755
- this.hideListLink?.classList.add('dpr-multiselect-action--hide');
1756
- }
1757
- }
1758
-
1759
- class AutoCompleteMulti extends DprClientClass {
1760
- filterId;
1761
- searchInput;
1762
- searchInputValue;
1763
- multiselectOptions;
1764
- static getModuleName() {
1765
- return 'autocomplete-multiselect-input';
1766
- }
1767
- initialise() {
1768
- this.element = this.getElement();
1769
- this.filterId = this.element.getAttribute('data-filter-id');
1770
- // Search input
1771
- this.searchInput = document.getElementById(`search.${this.filterId}`);
1772
- this.multiselectOptions = Array.from(this.element.querySelectorAll('.govuk-checkboxes__input'));
1773
- if (!this.searchInput || !this.multiselectOptions)
1774
- return;
1775
- this.initialiseCheckboxes();
1776
- this.initSearchInputAction();
1777
- }
1778
- initialiseCheckboxes() {
1779
- this.multiselectOptions.forEach(input => {
1780
- const wrapper = input.closest('.govuk-checkboxes__item');
1781
- if (!wrapper)
1782
- return;
1783
- wrapper.classList.toggle('dpr-form--hidden', !input.checked);
1784
- });
1785
- }
1786
- initSearchInputAction() {
1787
- if (!this.searchInput)
1788
- return;
1789
- const input = this.searchInput;
1790
- this.searchInput.addEventListener('keyup', _event => {
1791
- this.searchInputValue = input.value;
1792
- this.updateCheckboxes();
1793
- });
1794
- }
1795
- updateCheckboxes() {
1796
- const query = this.searchInputValue.toLowerCase().trim();
1797
- const minLength = 3;
1798
- this.multiselectOptions.forEach(input => {
1799
- const wrapper = input.closest('.govuk-checkboxes__item');
1800
- if (!wrapper)
1801
- return;
1802
- // Always show selected items
1803
- if (input.checked) {
1804
- wrapper.classList.remove('dpr-form--hidden');
1805
- return;
1806
- }
1807
- // Below threshold - hide unselected
1808
- if (query.length < minLength) {
1809
- wrapper.classList.add('dpr-form--hidden');
1810
- return;
1811
- }
1812
- const labelText = input.labels?.[0]?.innerText.toLowerCase() ?? '';
1813
- const matches = labelText.includes(query);
1814
- wrapper.classList.toggle('dpr-form--hidden', !matches);
1815
- });
1841
+ * @param {Event} e
1842
+ * @memberof MultiselectInput
1843
+ */
1844
+ removeFullWidthClasses(e) {
1845
+ e.preventDefault();
1846
+ this.element.classList.remove('multiselect-container__full-width');
1847
+ this.filtersContainer?.classList.remove('dpr-filter-item__span-3');
1848
+ // Update button visability
1849
+ this.fullListLink?.classList.remove('dpr-multiselect-action--hide');
1850
+ this.hideListLink?.classList.add('dpr-multiselect-action--hide');
1816
1851
  }
1817
1852
  }
1818
1853
 
@@ -18554,14 +18589,32 @@ var plugin = {
18554
18589
  }
18555
18590
  };
18556
18591
 
18557
- // @ts-nocheck
18558
18592
  /* eslint-disable class-methods-use-this */
18559
18593
  class ChartVisualisation extends DprClientClass {
18594
+ chartContext = null;
18595
+ chart = null;
18596
+ chartParams = {};
18597
+ type = null;
18598
+ id = '';
18599
+ unit = '';
18600
+ suffix = '';
18601
+ legend = null;
18602
+ tooltipDetailsEl = null;
18603
+ headlineValuesEl = null;
18604
+ labelElement = null;
18605
+ valueElement = null;
18606
+ legendElement = null;
18607
+ partialStart = false;
18608
+ partialEnd = false;
18609
+ singleDataset = false;
18610
+ static getModuleName() {
18611
+ return 'chart';
18612
+ }
18560
18613
  setupCanvas() {
18561
18614
  this.chartContext = this.getElement().querySelector('canvas');
18562
18615
  // data
18563
- this.id = this.chartContext.getAttribute('id');
18564
- this.chartParams = JSON.parse(this.getElement().getAttribute('data-dpr-chart-data'));
18616
+ this.id = this.chartContext?.getAttribute('id') || '';
18617
+ this.chartParams = JSON.parse(this.getElement().getAttribute('data-dpr-chart-data') || '{}');
18565
18618
  this.type = this.getElement().getAttribute('data-dpr-chart-type');
18566
18619
  this.setValueSuffix();
18567
18620
  // elements
@@ -18573,14 +18626,14 @@ class ChartVisualisation extends DprClientClass {
18573
18626
  this.valueElement = document.getElementById(`dpr-${this.id}-value`);
18574
18627
  this.legendElement = document.getElementById(`dpr-${this.id}-legend`);
18575
18628
  this.legendElement = document.getElementById(`dpr-${this.id}-legend`);
18576
- if (this.chartParams.partialDate) {
18577
- this.partialStart = this.chartParams.partialDate.start || false;
18578
- this.partialEnd = this.chartParams.partialDate.end || false;
18629
+ if (this.chartParams['partialDate']) {
18630
+ this.partialStart = this.chartParams['partialDate'].start || false;
18631
+ this.partialEnd = this.chartParams['partialDate'].end || false;
18579
18632
  }
18580
18633
  // flags
18581
- this.singleDataset = this.chartParams.datasets.length === 1;
18634
+ this.singleDataset = this.chartParams['datasets'].length === 1;
18582
18635
  }
18583
- initChart() {
18636
+ initChart(chartData) {
18584
18637
  // Prevent font loading issue
18585
18638
  window.addEventListener('load', () => {
18586
18639
  // An example of creating a chart, replace with your code:
@@ -18589,7 +18642,7 @@ class ChartVisualisation extends DprClientClass {
18589
18642
  Chart.register(plugin);
18590
18643
  Chart.register(MatrixController, MatrixElement);
18591
18644
  Chart.defaults.datasets.bar.categoryPercentage = 0.95;
18592
- this.chart = new Chart(this.chartContext, this.chartData);
18645
+ this.chart = new Chart(this.chartContext, chartData);
18593
18646
  this.initChartEvents();
18594
18647
  });
18595
18648
  }
@@ -18642,14 +18695,14 @@ class ChartVisualisation extends DprClientClass {
18642
18695
  }
18643
18696
  }
18644
18697
  setValueSuffix() {
18645
- this.unit = this.getElement().getAttribute('data-dpr-chart-unit');
18698
+ this.unit = this.getElement().getAttribute('data-dpr-chart-unit') || '';
18646
18699
  this.suffix = this.unit === 'percentage' ? '%' : '';
18647
18700
  }
18648
18701
  isPercentage() {
18649
18702
  return this.unit === 'percentage';
18650
18703
  }
18651
18704
  initChartEvents() {
18652
- this.chart.canvas.addEventListener('mouseout', e => {
18705
+ this.chart?.canvas.addEventListener('mouseout', () => {
18653
18706
  if (this.tooltipDetailsEl)
18654
18707
  this.tooltipDetailsEl.style.display = 'none';
18655
18708
  if (this.headlineValuesEl)
@@ -18658,10 +18711,11 @@ class ChartVisualisation extends DprClientClass {
18658
18711
  }
18659
18712
  }
18660
18713
 
18661
- // @ts-nocheck
18662
18714
  /* eslint-disable no-underscore-dangle */
18663
18715
  /* eslint-disable class-methods-use-this */
18664
18716
  class BarChartVisualisation extends ChartVisualisation {
18717
+ settings = {};
18718
+ chartData;
18665
18719
  static getModuleName() {
18666
18720
  return 'bar-chart';
18667
18721
  }
@@ -18701,9 +18755,9 @@ class BarChartVisualisation extends ChartVisualisation {
18701
18755
  return {
18702
18756
  color: '#FFF',
18703
18757
  display: () => {
18704
- return !this.timeseries;
18758
+ return true;
18705
18759
  },
18706
- formatter: value => {
18760
+ formatter: (value) => {
18707
18761
  return `${value}${this.suffix}`;
18708
18762
  },
18709
18763
  labels: {
@@ -18719,9 +18773,9 @@ class BarChartVisualisation extends ChartVisualisation {
18719
18773
  }
18720
18774
  }
18721
18775
 
18722
- // @ts-nocheck
18723
- /* eslint-disable class-methods-use-this */
18724
18776
  class DoughnutChartVisualisation extends ChartVisualisation {
18777
+ settings = {};
18778
+ chartData;
18725
18779
  static getModuleName() {
18726
18780
  return 'doughnut-chart';
18727
18781
  }
@@ -18741,7 +18795,7 @@ class DoughnutChartVisualisation extends ChartVisualisation {
18741
18795
  };
18742
18796
  }
18743
18797
  setOptions() {
18744
- const cutoutValue = this.chartParams.datasets.length === 1 ? '50%' : '20%';
18798
+ const cutoutValue = this.chartParams['datasets'].length === 1 ? '50%' : '20%';
18745
18799
  return {
18746
18800
  cutout: cutoutValue,
18747
18801
  };
@@ -18756,7 +18810,7 @@ class DoughnutChartVisualisation extends ChartVisualisation {
18756
18810
  }
18757
18811
  setPlugins() {
18758
18812
  const plugins = [];
18759
- if (this.chartParams.datasets.length === 1 && !this.isPercentage) {
18813
+ if (this.chartParams['datasets'].length === 1 && !this.isPercentage()) {
18760
18814
  plugins.push(this.setCentralText());
18761
18815
  }
18762
18816
  return plugins;
@@ -18766,16 +18820,14 @@ class DoughnutChartVisualisation extends ChartVisualisation {
18766
18820
  // Put the total in the center of the donut
18767
18821
  id: 'text',
18768
18822
  beforeDraw(chart) {
18769
- const { width } = chart;
18770
- const { height } = chart;
18771
- const { ctx } = chart;
18823
+ const { width, height, ctx } = chart;
18772
18824
  ctx.textBaseline = 'middle';
18773
18825
  let fontSize = 2.5;
18774
18826
  ctx.font = `100 ${fontSize}em GDS Transport`;
18775
18827
  ctx.fillStyle = ' #505a5f';
18776
18828
  // Accumulated total
18777
- const total = chart.data.datasets[0].data.reduce((a, c) => a + c, 0);
18778
- const text = total;
18829
+ const total = chart.data.datasets[0].data.reduce((a, c) => a + Number(c), 0);
18830
+ const text = total?.toString() || '';
18779
18831
  const textX = Math.round((width - ctx.measureText(text).width) / 2);
18780
18832
  const textY = height / 2;
18781
18833
  ctx.fillText(text, textX, textY);
@@ -18830,7 +18882,7 @@ class DoughnutChartVisualisation extends ChartVisualisation {
18830
18882
  return {
18831
18883
  textAlign: 'center',
18832
18884
  color: '#FFF',
18833
- display: context => {
18885
+ display: (context) => {
18834
18886
  const { dataset, dataIndex } = context;
18835
18887
  const value = dataset.data[dataIndex];
18836
18888
  const total = dataset.data.reduce((a, c) => a + c, 0);
@@ -18858,65 +18910,9 @@ ${dataset.label}`;
18858
18910
  }
18859
18911
  }
18860
18912
 
18861
- // @ts-nocheck
18862
- /* eslint-disable class-methods-use-this */
18863
- class LineChartVisualisation extends ChartVisualisation {
18864
- static getModuleName() {
18865
- return 'line-chart';
18866
- }
18867
- initialise() {
18868
- this.setupCanvas();
18869
- this.settings = this.initSettings();
18870
- this.chartData = this.generateChartData(this.settings);
18871
- this.lastIndex = this.chartData.data.labels.length - 1;
18872
- this.initChart(this.chartData);
18873
- }
18874
- initSettings() {
18875
- return {
18876
- toolTipOptions: this.setToolTipOptions(),
18877
- styling: this.setDatasetStyling(),
18878
- };
18879
- }
18880
- setPartialStyle(ctx) {
18881
- let style;
18882
- if ((this.partialEnd && ctx.p1DataIndex === this.lastIndex) || (this.partialStart && ctx.p1DataIndex === 1)) {
18883
- style = [6, 6];
18884
- }
18885
- return style;
18886
- }
18887
- setDatasetStyling() {
18888
- return {
18889
- segment: {
18890
- borderDash: ctx => this.setPartialStyle(ctx),
18891
- },
18892
- };
18893
- }
18894
- setToolTipOptions() {
18895
- const ctx = this;
18896
- return {
18897
- callbacks: {
18898
- title(context) {
18899
- const { label, dataset } = context[0];
18900
- const { label: establishmentId } = dataset;
18901
- const title = ctx.singleDataset ? `${label}` : `${establishmentId}: ${label}`;
18902
- return title;
18903
- },
18904
- label(context) {
18905
- const { label } = context;
18906
- const { data, label: legend } = context.dataset;
18907
- const value = data[context.dataIndex];
18908
- ctx.setHoverValue({ label, value, legend, ctx });
18909
- return value;
18910
- },
18911
- },
18912
- };
18913
- }
18914
- }
18915
-
18916
- // @ts-nocheck
18917
- /* eslint-disable prefer-destructuring */
18918
- /* eslint-disable class-methods-use-this */
18919
18913
  class MatrixChartVisualisation extends ChartVisualisation {
18914
+ settings = {};
18915
+ chartData;
18920
18916
  static getModuleName() {
18921
18917
  return 'matrix-chart';
18922
18918
  }
@@ -18936,7 +18932,7 @@ class MatrixChartVisualisation extends ChartVisualisation {
18936
18932
  return {
18937
18933
  callbacks: {
18938
18934
  title(context) {
18939
- const { raw } = context[0];
18935
+ const raw = context[0].raw;
18940
18936
  const title = `${raw.y} ${raw.x}`;
18941
18937
  return title;
18942
18938
  },
@@ -18985,23 +18981,82 @@ class MatrixChartVisualisation extends ChartVisualisation {
18985
18981
  }
18986
18982
  createDatasets() {
18987
18983
  const { datasets } = this.chartParams;
18988
- return datasets.map(d => {
18989
- const { label, data } = d;
18984
+ return datasets.map((dataset) => {
18985
+ const { label, data } = dataset;
18990
18986
  return {
18991
18987
  label,
18992
18988
  data,
18993
18989
  backgroundColor(c) {
18994
- return c.raw.c;
18990
+ const color = c.raw.c;
18991
+ return color;
18995
18992
  },
18996
- width: ({ chart }) => (chart.chartArea || {}).width / chart.scales.x.ticks.length - 1,
18997
- height: ({ chart }) => (chart.chartArea || {}).height / chart.scales.y.ticks.length - 1,
18993
+ width: ({ chart }) => (chart.chartArea || {}).width / chart.scales['x'].ticks.length - 1,
18994
+ height: ({ chart }) => (chart.chartArea || {}).height / chart.scales['y'].ticks.length - 1,
18998
18995
  };
18999
18996
  });
19000
18997
  }
19001
18998
  }
19002
18999
 
19003
- // @ts-nocheck
19000
+ class LineChartVisualisation extends ChartVisualisation {
19001
+ settings = {};
19002
+ chartData;
19003
+ lastIndex = 0;
19004
+ static getModuleName() {
19005
+ return 'line-chart';
19006
+ }
19007
+ initialise() {
19008
+ this.setupCanvas();
19009
+ this.settings = this.initSettings();
19010
+ this.chartData = this.generateChartData(this.settings);
19011
+ this.lastIndex = this.chartData.data?.labels ? this.chartData.data.labels.length - 1 : 0;
19012
+ this.initChart(this.chartData);
19013
+ }
19014
+ initSettings() {
19015
+ return {
19016
+ toolTipOptions: this.setToolTipOptions(),
19017
+ styling: this.setDatasetStyling(),
19018
+ };
19019
+ }
19020
+ setPartialStyle(ctx) {
19021
+ let style;
19022
+ if ((this.partialEnd && ctx.p1DataIndex === this.lastIndex) || (this.partialStart && ctx.p1DataIndex === 1)) {
19023
+ style = [6, 6];
19024
+ }
19025
+ return style;
19026
+ }
19027
+ setDatasetStyling() {
19028
+ return {
19029
+ segment: {
19030
+ borderDash: (ctx) => this.setPartialStyle(ctx),
19031
+ },
19032
+ };
19033
+ }
19034
+ setToolTipOptions() {
19035
+ const ctx = this;
19036
+ return {
19037
+ callbacks: {
19038
+ title(context) {
19039
+ const { label, dataset } = context[0];
19040
+ const { label: establishmentId } = dataset;
19041
+ const title = ctx.singleDataset ? `${label}` : `${establishmentId}: ${label}`;
19042
+ return title;
19043
+ },
19044
+ label(context) {
19045
+ const { label } = context;
19046
+ const { data, label: legend } = context.dataset;
19047
+ const value = String(data[context.dataIndex]);
19048
+ ctx.setHoverValue({ label, value, legend, ctx });
19049
+ return value;
19050
+ },
19051
+ },
19052
+ };
19053
+ }
19054
+ }
19055
+
19004
19056
  class Scorecard extends DprClientClass {
19057
+ scorecard = null;
19058
+ value = null;
19059
+ ragStatus = null;
19005
19060
  static getModuleName() {
19006
19061
  return 'scorecard';
19007
19062
  }
@@ -19013,15 +19068,39 @@ class Scorecard extends DprClientClass {
19013
19068
  this.initHover();
19014
19069
  }
19015
19070
  initHover() {
19016
- this.value.addEventListener('mouseover', async () => {
19017
- this.ragStatus.classList.add('dpr-scorecard__value-description--active');
19071
+ this.value?.addEventListener('mouseover', async () => {
19072
+ this.ragStatus?.classList.add('dpr-scorecard__value-description--active');
19018
19073
  });
19019
- this.value.addEventListener('mouseout', async () => {
19020
- this.ragStatus.classList.remove('dpr-scorecard__value-description--active');
19074
+ this.value?.addEventListener('mouseout', async () => {
19075
+ this.ragStatus?.classList.remove('dpr-scorecard__value-description--active');
19021
19076
  });
19022
19077
  }
19023
19078
  }
19024
19079
 
19080
+ class DprReportsCatalogueCollections extends DprClientClass {
19081
+ static getModuleName() {
19082
+ return 'dpr-reports-catalogue-collections';
19083
+ }
19084
+ initialise() {
19085
+ this.initProductCollectionSelect();
19086
+ }
19087
+ initProductCollectionSelect() {
19088
+ const element = this.getElement();
19089
+ if (element) {
19090
+ const productCollections = element.querySelector('#productCollection');
19091
+ if (productCollections) {
19092
+ productCollections.addEventListener('change', e => {
19093
+ e.preventDefault();
19094
+ const form = productCollections.closest('form');
19095
+ if (!form)
19096
+ return;
19097
+ form.submit();
19098
+ });
19099
+ }
19100
+ }
19101
+ }
19102
+ }
19103
+
19025
19104
  class DprReportsCatalogueFiltersClass extends DprClientClass {
19026
19105
  static products;
19027
19106
  static variants;
@@ -19162,163 +19241,59 @@ class DprReportsCatalogueSearch extends DprReportsCatalogueFiltersClass {
19162
19241
  }).length;
19163
19242
  }
19164
19243
  variantMatches(product, variant, searchTerm) {
19165
- if (searchTerm === '') {
19166
- return true;
19167
- }
19168
- const heading = product.querySelector('.dpr-report-catalogue__product-row__name');
19169
- const productText = heading ? this.getSearchText(heading) : '';
19170
- const variantText = this.getSearchText(variant);
19171
- const searchTerms = searchTerm.split(/\s+/).filter(Boolean);
19172
- return searchTerms.every(term => productText.includes(term) || variantText.includes(term));
19173
- }
19174
- /**
19175
- * Determines whether a product heading matches the search term.
19176
- */
19177
- productMatches(product, searchTerm) {
19178
- const heading = product.querySelector('.dpr-report-catalogue__product-row__name');
19179
- return heading ? this.matchesSearch(this.getSearchText(heading), searchTerm) : false;
19180
- }
19181
- matchesSearch(text, searchTerm) {
19182
- if (searchTerm === '') {
19183
- return true;
19184
- }
19185
- const searchTerms = searchTerm.split(/\s+/).filter(Boolean);
19186
- return searchTerms.every(term => text.includes(term));
19187
- }
19188
- /**
19189
- * Normalises a search term for matching.
19190
- *
19191
- * Searches with fewer than three characters are treated as empty.
19192
- */
19193
- normaliseSearch(search) {
19194
- const value = search.trim().toLowerCase();
19195
- return value.length >= 3 ? value : '';
19196
- }
19197
- /**
19198
- * Reads the current search term from the URL query string.
19199
- */
19200
- getSearchFromQueryString() {
19201
- const params = new URLSearchParams(window.location.search);
19202
- return params.get('search') ?? '';
19203
- }
19204
- /**
19205
- * Updates the URL query string with the current search value.
19206
- *
19207
- * The parameter is removed when the search is cleared or contains
19208
- * fewer than three characters.
19209
- */
19210
- updateQueryString(search) {
19211
- const params = new URLSearchParams(window.location.search);
19212
- const value = search.trim();
19213
- if (value.length >= 3) {
19214
- params.set('search', value);
19215
- }
19216
- else {
19217
- params.delete('search');
19218
- }
19219
- const queryString = params.toString();
19220
- window.history.replaceState({}, '', queryString ? `${window.location.pathname}?${queryString}` : window.location.pathname);
19221
- }
19222
- }
19223
-
19224
- class DprReportsCatalogueCollections extends DprClientClass {
19225
- static getModuleName() {
19226
- return 'dpr-reports-catalogue-collections';
19227
- }
19228
- initialise() {
19229
- this.initProductCollectionSelect();
19230
- }
19231
- initProductCollectionSelect() {
19232
- const element = this.getElement();
19233
- if (element) {
19234
- const productCollections = element.querySelector('#productCollection');
19235
- if (productCollections) {
19236
- productCollections.addEventListener('change', e => {
19237
- e.preventDefault();
19238
- const form = productCollections.closest('form');
19239
- if (!form)
19240
- return;
19241
- form.submit();
19242
- });
19243
- }
19244
- }
19245
- }
19246
- }
19247
-
19248
- /**
19249
- * Client-side report type filter for the reports catalogue.
19250
- *
19251
- * Filters variant/dashboard rows by report type using the
19252
- * `data-report-type` attribute and applies the `type-hide`
19253
- * CSS class to rows that should not be visible.
19254
- *
19255
- * The selected filter is persisted in the URL query string
19256
- * so that refreshes and shared links retain the current state.
19257
- */
19258
- class DprReportsCatalogueTypeFilter extends DprReportsCatalogueFiltersClass {
19259
- reportTypeRadios;
19260
- showHideClassName = 'dpr-reports-catalogue-type-hide';
19261
- static getModuleName() {
19262
- return 'dpr-report-catalogue-type-filter';
19263
- }
19264
- initialise() {
19265
- this.reportTypeRadios = [...this.getElement().querySelectorAll('input[type="radio"]')];
19266
- if (this.reportTypeRadios.length === 0) {
19267
- return;
19268
- }
19269
- const selectedType = this.getReportTypeFromQueryString();
19270
- const selectedRadio = this.reportTypeRadios.find(radio => radio.value === selectedType);
19271
- if (selectedRadio) {
19272
- selectedRadio.checked = true;
19273
- }
19274
- this.applyFilter(selectedType);
19275
- this.reportTypeRadios.forEach(radio => {
19276
- radio.addEventListener('change', () => {
19277
- this.updateQueryString(radio.value);
19278
- this.applyFilter(radio.value);
19279
- });
19280
- });
19244
+ if (searchTerm === '') {
19245
+ return true;
19246
+ }
19247
+ const heading = product.querySelector('.dpr-report-catalogue__product-row__name');
19248
+ const productText = heading ? this.getSearchText(heading) : '';
19249
+ const variantText = this.getSearchText(variant);
19250
+ const searchTerms = searchTerm.split(/\s+/).filter(Boolean);
19251
+ return searchTerms.every(term => productText.includes(term) || variantText.includes(term));
19281
19252
  }
19282
19253
  /**
19283
- * Applies the selected report type filter to all products.
19254
+ * Determines whether a product heading matches the search term.
19284
19255
  */
19285
- applyFilter(reportType) {
19286
- this.getProducts().forEach(product => {
19287
- this.filterProduct(product, reportType);
19288
- });
19289
- document.dispatchEvent(new CustomEvent('dpr-report-catalogue-filter-changed'));
19256
+ productMatches(product, searchTerm) {
19257
+ const heading = product.querySelector('.dpr-report-catalogue__product-row__name');
19258
+ return heading ? this.matchesSearch(this.getSearchText(heading), searchTerm) : false;
19259
+ }
19260
+ matchesSearch(text, searchTerm) {
19261
+ if (searchTerm === '') {
19262
+ return true;
19263
+ }
19264
+ const searchTerms = searchTerm.split(/\s+/).filter(Boolean);
19265
+ return searchTerms.every(term => text.includes(term));
19290
19266
  }
19291
19267
  /**
19292
- * Filters all rows belonging to a product and determines
19293
- * whether the product itself should remain visible.
19268
+ * Normalises a search term for matching.
19269
+ *
19270
+ * Searches with fewer than three characters are treated as empty.
19294
19271
  */
19295
- filterProduct(product, reportType) {
19296
- const visibleRows = this.getProductVariants(product).filter(row => {
19297
- const rowType = row.dataset['reportType'];
19298
- const showRow = reportType === '' || reportType === 'all' || rowType === reportType;
19299
- row.classList.toggle(this.showHideClassName, !showRow);
19300
- return showRow;
19301
- });
19302
- product.classList.toggle(this.showHideClassName, visibleRows.length === 0);
19272
+ normaliseSearch(search) {
19273
+ const value = search.trim().toLowerCase();
19274
+ return value.length >= 3 ? value : '';
19303
19275
  }
19304
19276
  /**
19305
- * Reads the selected report type from the URL query string.
19277
+ * Reads the current search term from the URL query string.
19306
19278
  */
19307
- getReportTypeFromQueryString() {
19279
+ getSearchFromQueryString() {
19308
19280
  const params = new URLSearchParams(window.location.search);
19309
- return params.get('report-type') ?? 'all';
19281
+ return params.get('search') ?? '';
19310
19282
  }
19311
19283
  /**
19312
- * Updates the URL query string with the currently
19313
- * selected report type.
19284
+ * Updates the URL query string with the current search value.
19285
+ *
19286
+ * The parameter is removed when the search is cleared or contains
19287
+ * fewer than three characters.
19314
19288
  */
19315
- updateQueryString(reportType) {
19289
+ updateQueryString(search) {
19316
19290
  const params = new URLSearchParams(window.location.search);
19317
- if (reportType && reportType !== 'all') {
19318
- params.set('report-type', reportType);
19291
+ const value = search.trim();
19292
+ if (value.length >= 3) {
19293
+ params.set('search', value);
19319
19294
  }
19320
19295
  else {
19321
- params.delete('report-type');
19296
+ params.delete('search');
19322
19297
  }
19323
19298
  const queryString = params.toString();
19324
19299
  window.history.replaceState({}, '', queryString ? `${window.location.pathname}?${queryString}` : window.location.pathname);
@@ -19449,80 +19424,82 @@ class DprReportsCatalogueShowHide extends DprReportsCatalogueFiltersClass {
19449
19424
  }
19450
19425
 
19451
19426
  /**
19452
- * Updates catalogue totals whenever filters change.
19427
+ * Client-side report type filter for the reports catalogue.
19453
19428
  *
19454
- * Counts only visible products and variants.
19429
+ * Filters variant/dashboard rows by report type using the
19430
+ * `data-report-type` attribute and applies the `type-hide`
19431
+ * CSS class to rows that should not be visible.
19432
+ *
19433
+ * The selected filter is persisted in the URL query string
19434
+ * so that refreshes and shared links retain the current state.
19455
19435
  */
19456
- class DprReportsCatalogueTotals extends DprReportsCatalogueFiltersClass {
19457
- productsTotal;
19458
- variantsTotal;
19436
+ class DprReportsCatalogueTypeFilter extends DprReportsCatalogueFiltersClass {
19437
+ reportTypeRadios;
19438
+ showHideClassName = 'dpr-reports-catalogue-type-hide';
19459
19439
  static getModuleName() {
19460
- return 'dpr-report-catalogue-totals';
19440
+ return 'dpr-report-catalogue-type-filter';
19461
19441
  }
19462
19442
  initialise() {
19463
- this.productsTotal = this.getElement().querySelector('[data-products-total]');
19464
- this.variantsTotal = this.getElement().querySelector('[data-products-variants]');
19465
- this.updateTotals();
19466
- document.addEventListener('dpr-report-catalogue-filter-changed', () => {
19467
- this.updateTotals();
19468
- });
19469
- }
19470
- /**
19471
- * Updates visible product and variant counts.
19472
- */
19473
- updateTotals() {
19474
- if (!this.productsTotal || !this.variantsTotal) {
19443
+ this.reportTypeRadios = [...this.getElement().querySelectorAll('input[type="radio"]')];
19444
+ if (this.reportTypeRadios.length === 0) {
19475
19445
  return;
19476
19446
  }
19477
- const variantCount = this.getVisibleVariants().length;
19478
- this.variantsTotal.innerHTML = `<strong>${variantCount}</strong> ${this.pluralise(variantCount, 'report', 'reports')}`;
19479
- const productCount = this.getVisibleProducts().length;
19480
- this.productsTotal.innerHTML = `<strong>${productCount}</strong> ${this.pluralise(productCount, 'product', 'products')}`;
19447
+ const selectedType = this.getReportTypeFromQueryString();
19448
+ const selectedRadio = this.reportTypeRadios.find(radio => radio.value === selectedType);
19449
+ if (selectedRadio) {
19450
+ selectedRadio.checked = true;
19451
+ }
19452
+ this.applyFilter(selectedType);
19453
+ this.reportTypeRadios.forEach(radio => {
19454
+ radio.addEventListener('change', () => {
19455
+ this.updateQueryString(radio.value);
19456
+ this.applyFilter(radio.value);
19457
+ });
19458
+ });
19481
19459
  }
19482
19460
  /**
19483
- * Returns all visible products.
19461
+ * Applies the selected report type filter to all products.
19484
19462
  */
19485
- getVisibleProducts() {
19486
- return this.getProducts().filter(product => this.isVisibleProduct(product));
19463
+ applyFilter(reportType) {
19464
+ this.getProducts().forEach(product => {
19465
+ this.filterProduct(product, reportType);
19466
+ });
19467
+ document.dispatchEvent(new CustomEvent('dpr-report-catalogue-filter-changed'));
19487
19468
  }
19488
19469
  /**
19489
- * Returns all visible variants.
19470
+ * Filters all rows belonging to a product and determines
19471
+ * whether the product itself should remain visible.
19490
19472
  */
19491
- getVisibleVariants() {
19492
- return this.getVariants().filter(variant => this.isVisibleVariant(variant));
19493
- }
19494
- pluralise(count, singular, plural) {
19495
- return count === 1 ? singular : plural;
19496
- }
19497
- }
19498
-
19499
- class DprReportsCatalogueProductCounts extends DprReportsCatalogueFiltersClass {
19500
- static getModuleName() {
19501
- return 'dpr-report-catalogue-product-counts';
19502
- }
19503
- initialise() {
19504
- this.updateCounts();
19505
- document.addEventListener('dpr-report-catalogue-filter-changed', () => {
19506
- this.updateCounts();
19507
- });
19508
- }
19509
- updateCounts() {
19510
- this.getProducts().forEach(product => {
19511
- const count = this.getVisibleVariantCount(product);
19512
- const totalElement = product.querySelector('[data-product-variants-total]');
19513
- if (totalElement) {
19514
- totalElement.textContent = `(${this.getVariantLabel(count)})`;
19515
- }
19473
+ filterProduct(product, reportType) {
19474
+ const visibleRows = this.getProductVariants(product).filter(row => {
19475
+ const rowType = row.dataset['reportType'];
19476
+ const showRow = reportType === '' || reportType === 'all' || rowType === reportType;
19477
+ row.classList.toggle(this.showHideClassName, !showRow);
19478
+ return showRow;
19516
19479
  });
19480
+ product.classList.toggle(this.showHideClassName, visibleRows.length === 0);
19517
19481
  }
19518
- getVariantLabel(count) {
19519
- return `${count} report${count === 1 ? '' : 's'}`;
19482
+ /**
19483
+ * Reads the selected report type from the URL query string.
19484
+ */
19485
+ getReportTypeFromQueryString() {
19486
+ const params = new URLSearchParams(window.location.search);
19487
+ return params.get('report-type') ?? 'all';
19520
19488
  }
19521
- getVisibleVariantCount(product) {
19522
- if (!this.isVisibleProduct(product)) {
19523
- return 0;
19489
+ /**
19490
+ * Updates the URL query string with the currently
19491
+ * selected report type.
19492
+ */
19493
+ updateQueryString(reportType) {
19494
+ const params = new URLSearchParams(window.location.search);
19495
+ if (reportType && reportType !== 'all') {
19496
+ params.set('report-type', reportType);
19524
19497
  }
19525
- return [...product.querySelectorAll('.dpr-report-catalogue__variant-row')].filter(row => this.isVisibleVariant(row)).length;
19498
+ else {
19499
+ params.delete('report-type');
19500
+ }
19501
+ const queryString = params.toString();
19502
+ window.history.replaceState({}, '', queryString ? `${window.location.pathname}?${queryString}` : window.location.pathname);
19526
19503
  }
19527
19504
  }
19528
19505
 
@@ -19583,7 +19560,84 @@ class DprReportsCatalogueNavigation extends DprReportsCatalogueFiltersClass {
19583
19560
  }
19584
19561
  }
19585
19562
 
19586
- // @ts-nocheck
19563
+ class DprReportsCatalogueProductCounts extends DprReportsCatalogueFiltersClass {
19564
+ static getModuleName() {
19565
+ return 'dpr-report-catalogue-product-counts';
19566
+ }
19567
+ initialise() {
19568
+ this.updateCounts();
19569
+ document.addEventListener('dpr-report-catalogue-filter-changed', () => {
19570
+ this.updateCounts();
19571
+ });
19572
+ }
19573
+ updateCounts() {
19574
+ this.getProducts().forEach(product => {
19575
+ const count = this.getVisibleVariantCount(product);
19576
+ const totalElement = product.querySelector('[data-product-variants-total]');
19577
+ if (totalElement) {
19578
+ totalElement.textContent = `(${this.getVariantLabel(count)})`;
19579
+ }
19580
+ });
19581
+ }
19582
+ getVariantLabel(count) {
19583
+ return `${count} report${count === 1 ? '' : 's'}`;
19584
+ }
19585
+ getVisibleVariantCount(product) {
19586
+ if (!this.isVisibleProduct(product)) {
19587
+ return 0;
19588
+ }
19589
+ return [...product.querySelectorAll('.dpr-report-catalogue__variant-row')].filter(row => this.isVisibleVariant(row)).length;
19590
+ }
19591
+ }
19592
+
19593
+ /**
19594
+ * Updates catalogue totals whenever filters change.
19595
+ *
19596
+ * Counts only visible products and variants.
19597
+ */
19598
+ class DprReportsCatalogueTotals extends DprReportsCatalogueFiltersClass {
19599
+ productsTotal;
19600
+ variantsTotal;
19601
+ static getModuleName() {
19602
+ return 'dpr-report-catalogue-totals';
19603
+ }
19604
+ initialise() {
19605
+ this.productsTotal = this.getElement().querySelector('[data-products-total]');
19606
+ this.variantsTotal = this.getElement().querySelector('[data-products-variants]');
19607
+ this.updateTotals();
19608
+ document.addEventListener('dpr-report-catalogue-filter-changed', () => {
19609
+ this.updateTotals();
19610
+ });
19611
+ }
19612
+ /**
19613
+ * Updates visible product and variant counts.
19614
+ */
19615
+ updateTotals() {
19616
+ if (!this.productsTotal || !this.variantsTotal) {
19617
+ return;
19618
+ }
19619
+ const variantCount = this.getVisibleVariants().length;
19620
+ this.variantsTotal.innerHTML = `<strong>${variantCount}</strong> ${this.pluralise(variantCount, 'report', 'reports')}`;
19621
+ const productCount = this.getVisibleProducts().length;
19622
+ this.productsTotal.innerHTML = `<strong>${productCount}</strong> ${this.pluralise(productCount, 'product', 'products')}`;
19623
+ }
19624
+ /**
19625
+ * Returns all visible products.
19626
+ */
19627
+ getVisibleProducts() {
19628
+ return this.getProducts().filter(product => this.isVisibleProduct(product));
19629
+ }
19630
+ /**
19631
+ * Returns all visible variants.
19632
+ */
19633
+ getVisibleVariants() {
19634
+ return this.getVariants().filter(variant => this.isVisibleVariant(variant));
19635
+ }
19636
+ pluralise(count, singular, plural) {
19637
+ return count === 1 ? singular : plural;
19638
+ }
19639
+ }
19640
+
19587
19641
  /* eslint-disable no-new */
19588
19642
  /* global dayjs */
19589
19643
  /**