@spokane-folio/security-incident 1.0.28

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.
Files changed (188) hide show
  1. package/.eslintrc +32 -0
  2. package/.github/workflows/CODEOWNERS +8 -0
  3. package/.github/workflows/pr-validation.yml +44 -0
  4. package/.github/workflows/release.yml +64 -0
  5. package/.prettierrc +6 -0
  6. package/.stripesclirc +4 -0
  7. package/CHANGELOG.md +8 -0
  8. package/CONTRIBUTING.md +4 -0
  9. package/LICENSE +201 -0
  10. package/README.md +16 -0
  11. package/administrator-documentation/roles-and-permissions.md +65 -0
  12. package/administrator-documentation/track-settings-admin-guide-sketch.md +192 -0
  13. package/administrator-documentation/using-the-application.md +192 -0
  14. package/icons/app.png +0 -0
  15. package/icons/app.svg +1 -0
  16. package/icons/playButton.png +0 -0
  17. package/icons/profilePicThumbnail.png +0 -0
  18. package/jest.config.js +10 -0
  19. package/module-descriptor.json +75 -0
  20. package/output/service-worker.js +0 -0
  21. package/package.json +146 -0
  22. package/src/components/incidents/ColumnChooser.js +37 -0
  23. package/src/components/incidents/CreateMedia.js +132 -0
  24. package/src/components/incidents/CreatePane.js +1215 -0
  25. package/src/components/incidents/CreatePane.test.js +138 -0
  26. package/src/components/incidents/CreateReport.js +102 -0
  27. package/src/components/incidents/DetailsPane.js +1267 -0
  28. package/src/components/incidents/DetailsPane.test.js +150 -0
  29. package/src/components/incidents/EditPane.js +2334 -0
  30. package/src/components/incidents/EditPane.test.js +187 -0
  31. package/src/components/incidents/GetDetails.js +55 -0
  32. package/src/components/incidents/GetListDQLinkIncident.js +81 -0
  33. package/src/components/incidents/GetListDynamicQuery.js +66 -0
  34. package/src/components/incidents/GetLocations.js +57 -0
  35. package/src/components/incidents/GetMedia.js +98 -0
  36. package/src/components/incidents/GetName.js +111 -0
  37. package/src/components/incidents/GetNameCreatedBy.js +94 -0
  38. package/src/components/incidents/GetOrgLocaleSettings.js +61 -0
  39. package/src/components/incidents/GetPatronGroups.js +52 -0
  40. package/src/components/incidents/GetSelf.js +65 -0
  41. package/src/components/incidents/GetSummary.js +110 -0
  42. package/src/components/incidents/IncidentTypeCard.js +53 -0
  43. package/src/components/incidents/IncidentTypeCard.test.js +133 -0
  44. package/src/components/incidents/IncidentsPaneset.js +810 -0
  45. package/src/components/incidents/IncidentsPaneset.test.js +128 -0
  46. package/src/components/incidents/LinkedIncident.js +86 -0
  47. package/src/components/incidents/ModalAddMedia.js +262 -0
  48. package/src/components/incidents/ModalAddMedia.test.js +97 -0
  49. package/src/components/incidents/ModalAttentionDecOfService.js +111 -0
  50. package/src/components/incidents/ModalCustomWitness.js +469 -0
  51. package/src/components/incidents/ModalCustomWitness.test.js +147 -0
  52. package/src/components/incidents/ModalCustomerDetails.js +480 -0
  53. package/src/components/incidents/ModalCustomerDetails.test.js +116 -0
  54. package/src/components/incidents/ModalDescribeCustomer.js +361 -0
  55. package/src/components/incidents/ModalDescribeCustomer.test.js +156 -0
  56. package/src/components/incidents/ModalDirtyFormWarn.js +62 -0
  57. package/src/components/incidents/ModalLinkIncident.js +1213 -0
  58. package/src/components/incidents/ModalLinkIncidentStyle.css +32 -0
  59. package/src/components/incidents/ModalSelectIncidentTypes.js +178 -0
  60. package/src/components/incidents/ModalSelectIncidentTypes.test.js +273 -0
  61. package/src/components/incidents/ModalSelectKnownCustomer.js +395 -0
  62. package/src/components/incidents/ModalSelectWitness.js +406 -0
  63. package/src/components/incidents/ModalSelectWitness.test.js +308 -0
  64. package/src/components/incidents/ModalStyle.css +44 -0
  65. package/src/components/incidents/ModalTrespass.js +741 -0
  66. package/src/components/incidents/ModalViewCustomerDetails.js +241 -0
  67. package/src/components/incidents/ModalViewMedia.js +86 -0
  68. package/src/components/incidents/ModalViewTrespass.js +210 -0
  69. package/src/components/incidents/ResultsPane.js +437 -0
  70. package/src/components/incidents/ResultsPane.test.js +120 -0
  71. package/src/components/incidents/SearchCustomerOrWitness.js +108 -0
  72. package/src/components/incidents/Thumbnail.js +72 -0
  73. package/src/components/incidents/ThumbnailMarkRemoval.js +38 -0
  74. package/src/components/incidents/ThumbnailSkeleton.js +30 -0
  75. package/src/components/incidents/ThumbnailStyles.js +49 -0
  76. package/src/components/incidents/ThumbnailTempPreSave.js +71 -0
  77. package/src/components/incidents/UpdateReport.js +84 -0
  78. package/src/components/incidents/__snapshots__/CreatePane.test.js.snap +3 -0
  79. package/src/components/incidents/__snapshots__/DetailsPane.test.js.snap +3 -0
  80. package/src/components/incidents/__snapshots__/EditPane.test.js.snap +3 -0
  81. package/src/components/incidents/__snapshots__/IncidentTypeCard.test.js.snap +3 -0
  82. package/src/components/incidents/__snapshots__/IncidentsPaneset.test.js.snap +3 -0
  83. package/src/components/incidents/__snapshots__/ModalAddMedia.test.js.snap +3 -0
  84. package/src/components/incidents/__snapshots__/ModalCustomerDetails.test.js.snap +3 -0
  85. package/src/components/incidents/__snapshots__/ModalSelectWitness.test.js.snap +3 -0
  86. package/src/components/incidents/__snapshots__/ResultsPane.test.js.snap +3 -0
  87. package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.css +5 -0
  88. package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.js +51 -0
  89. package/src/components/incidents/helpers/ProfilePicture/isAValidURL.js +3 -0
  90. package/src/components/incidents/helpers/ProfilePicture/useProfilePicture.js +127 -0
  91. package/src/components/incidents/helpers/buildQueryString.js +28 -0
  92. package/src/components/incidents/helpers/cleanFormValues.js +53 -0
  93. package/src/components/incidents/helpers/computeEditedCustomers.js +124 -0
  94. package/src/components/incidents/helpers/convertDateIgnoringTZ.js +8 -0
  95. package/src/components/incidents/helpers/convertUTCISOToLocalePrettyTime.js +15 -0
  96. package/src/components/incidents/helpers/convertUTCISOToPrettyDate.js +19 -0
  97. package/src/components/incidents/helpers/decodeParamsToForm.js +20 -0
  98. package/src/components/incidents/helpers/deepNormalizeForComparison.js +39 -0
  99. package/src/components/incidents/helpers/extractFilterString.js +12 -0
  100. package/src/components/incidents/helpers/formatDateAndTimeToUTCISO.js +14 -0
  101. package/src/components/incidents/helpers/formatDateToUTCISO.js +14 -0
  102. package/src/components/incidents/helpers/formatTimeToUTCISO.js +28 -0
  103. package/src/components/incidents/helpers/getCurrentTime.js +20 -0
  104. package/src/components/incidents/helpers/getTodayDate.js +12 -0
  105. package/src/components/incidents/helpers/handlebarsHelpers.js +148 -0
  106. package/src/components/incidents/helpers/hasFormChangedAtCreate.js +50 -0
  107. package/src/components/incidents/helpers/hasTopLevelChangeAffectedDeclaration.js +90 -0
  108. package/src/components/incidents/helpers/hasTopLevelFormChanged.js +111 -0
  109. package/src/components/incidents/helpers/identifyCurrentTrespassDocs.js +109 -0
  110. package/src/components/incidents/helpers/isSameHtml.js +13 -0
  111. package/src/components/incidents/helpers/isValidDateFormat.js +14 -0
  112. package/src/components/incidents/helpers/isValidTimeInput.js +11 -0
  113. package/src/components/incidents/helpers/isValidUTCTimeFormat.js +14 -0
  114. package/src/components/incidents/helpers/parseMMDDYYYY.js +7 -0
  115. package/src/components/incidents/helpers/parseQueryString.js +16 -0
  116. package/src/components/incidents/helpers/sortTrespassDocuments.js +44 -0
  117. package/src/components/incidents/helpers/stripHTML.js +11 -0
  118. package/src/components/incidents/helpers/trespassDocUtils.js +197 -0
  119. package/src/components/incidents/helpers/validateTrespassDetails.js +37 -0
  120. package/src/components/incidents/usePersistedColModalLink.js +70 -0
  121. package/src/components/incidents/usePersistedColumns.js +70 -0
  122. package/src/components/incidents/usePersistedSort.js +23 -0
  123. package/src/components/incidents/usePersistedSortModalLink.js +23 -0
  124. package/src/contexts/IncidentContext.js +433 -0
  125. package/src/index.js +61 -0
  126. package/src/routes/Application.js +13 -0
  127. package/src/settings/GetIncidentCategories.js +56 -0
  128. package/src/settings/GetIncidentTypesDetails.js +88 -0
  129. package/src/settings/GetIncidentTypesIds.js +74 -0
  130. package/src/settings/GetLocationsInService.js +54 -0
  131. package/src/settings/GetSingleCustomLocationDetails.js +60 -0
  132. package/src/settings/GetSingleIncidentTypeDetails.js +60 -0
  133. package/src/settings/GetTrespassReasons.js +67 -0
  134. package/src/settings/GetTrespassTemplates.js +51 -0
  135. package/src/settings/IncidentCategoriesPane.js +285 -0
  136. package/src/settings/IncidentCategoriesPane.test.js +229 -0
  137. package/src/settings/IncidentTypeDetailsPane.js +215 -0
  138. package/src/settings/IncidentTypeDetailsPane.test.js +220 -0
  139. package/src/settings/IncidentTypeEditPane.js +211 -0
  140. package/src/settings/IncidentTypeEditPane.test.js +170 -0
  141. package/src/settings/IncidentTypesPaneset.js +167 -0
  142. package/src/settings/IncidentTypesPaneset.test.js +124 -0
  143. package/src/settings/LocationInServiceEditPane.js +320 -0
  144. package/src/settings/LocationsPaneset.js +415 -0
  145. package/src/settings/LocationsPaneset.test.js +106 -0
  146. package/src/settings/ModalDeleteCategory.js +47 -0
  147. package/src/settings/ModalDeleteIncidentType.js +49 -0
  148. package/src/settings/ModalDeleteLocationInService.js +49 -0
  149. package/src/settings/ModalDeleteTrespassReason.js +49 -0
  150. package/src/settings/ModalPreviewTrespassDoc.js +65 -0
  151. package/src/settings/ModalTrespassDocTokens.js +83 -0
  152. package/src/settings/NewIncidentTypePane.js +182 -0
  153. package/src/settings/PutIncidentType.js +60 -0
  154. package/src/settings/PutLocationsInService.js +52 -0
  155. package/src/settings/PutTrespassReasons.js +61 -0
  156. package/src/settings/PutTrespassTemplate.js +50 -0
  157. package/src/settings/TrespassDoc.css +17 -0
  158. package/src/settings/TrespassDocDetailsPane.js +215 -0
  159. package/src/settings/TrespassDocEditPane.js +538 -0
  160. package/src/settings/TrespassDocPaneset.js +581 -0
  161. package/src/settings/TrespassReasonDetailsPane.js +171 -0
  162. package/src/settings/TrespassReasonEditPane.js +221 -0
  163. package/src/settings/TrespassReasonsPaneset.js +282 -0
  164. package/src/settings/__snapshots__/IncidentCategoriesPane.test.js.snap +3 -0
  165. package/src/settings/__snapshots__/IncidentTypeDetailsPane.test.js.snap +3 -0
  166. package/src/settings/__snapshots__/IncidentTypeEditPane.test.js.snap +3 -0
  167. package/src/settings/__snapshots__/IncidentTypesPaneset.test.js.snap +3 -0
  168. package/src/settings/__snapshots__/LocationsPaneset.test.js.snap +3 -0
  169. package/src/settings/data/exampleJSON.json +92 -0
  170. package/src/settings/data/templateTokens.js +396 -0
  171. package/src/settings/helpers/alphabetize.js +18 -0
  172. package/src/settings/helpers/getCategoryTitleById.js +13 -0
  173. package/src/settings/helpers/makeId.js +15 -0
  174. package/src/settings/index.js +48 -0
  175. package/stripes.config.js +10 -0
  176. package/test/jest/__mock__/index.js +8 -0
  177. package/test/jest/__mock__/intl.mock.js +27 -0
  178. package/test/jest/__mock__/stripes.mock.js +26 -0
  179. package/test/jest/__mock__/stripesComponents.mock.js +151 -0
  180. package/test/jest/__mock__/stripesConfig.mock.js +1 -0
  181. package/test/jest/__mock__/stripesCore.mock.js +9 -0
  182. package/test/jest/__mock__/stripesIcon.mock.js +5 -0
  183. package/test/jest/__mock__/stripesSmartComponents.mock.js +7 -0
  184. package/test/jest/__mock__/stripesUtils.mock.js +3 -0
  185. package/test/jest/eslintrc.js +12 -0
  186. package/test/jest/setupFiles.js +5 -0
  187. package/translations/ui-security-incident/en_US.json +542 -0
  188. package/ui-module-acceptance-criteria.md +34 -0
@@ -0,0 +1,1213 @@
1
+ import React, { useState, useEffect, useMemo, useCallback } from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import { useIntl } from 'react-intl';
4
+ import {
5
+ Accordion,
6
+ AutoSuggest,
7
+ Checkbox,
8
+ Paneset,
9
+ Pane,
10
+ SearchField,
11
+ Button,
12
+ Headline,
13
+ Datepicker,
14
+ Row,
15
+ Col,
16
+ Icon,
17
+ TextField,
18
+ RadioButton,
19
+ Modal,
20
+ ModalFooter,
21
+ MultiColumnList,
22
+ PaneHeader,
23
+ LoadingPane,
24
+ } from '@folio/stripes/components';
25
+ import css from './ModalLinkIncidentStyle.css';
26
+ import GetLocationsInService from '../../settings/GetLocationsInService';
27
+ import GetListDQLinkIncident from './GetListDQLinkIncident';
28
+ import GetLocations from './GetLocations';
29
+ import GetIncidentTypesDetails from '../../settings/GetIncidentTypesDetails';
30
+ import buildQueryString from './helpers/buildQueryString.js';
31
+ import cleanFormValues from './helpers/cleanFormValues.js';
32
+ import GetOrgLocaleSettings from './GetOrgLocaleSettings.js';
33
+ import usePersistedSortModalLink from './usePersistedSortModalLink.js';
34
+ import convertUTCISOToPrettyDate from './helpers/convertUTCISOToPrettyDate';
35
+ import ColumnChooser from './ColumnChooser.js';
36
+ import usePersistedColModalLink from './usePersistedColModalLink.js';
37
+ import { useIncidents } from '../../contexts/IncidentContext';
38
+
39
+
40
+ const ModalLinkIncident = ({
41
+ handleCloseModalLinkIncident,
42
+ toggleRowChecked,
43
+ ids, // linkedTo ids (CreatePane passes selectedIds, EditPane passes allLinkedToArray)
44
+ setIds, // setter for ids (CreatePane passes setSelectedIds, EditPane passes setAllLinkedToArray)
45
+ ...props
46
+ }) => {
47
+ const intl = useIntl();
48
+ const {
49
+ incidentTypesNamesIdsList,
50
+ locationsInService,
51
+ isLoadingSearch,
52
+ organizationTimezone,
53
+ } = useIncidents();
54
+
55
+ const {
56
+ sortColumn,
57
+ sortDirection,
58
+ setSortColumn,
59
+ setSortDirection
60
+ } = usePersistedSortModalLink();
61
+
62
+ const [limit, setLimit] = useState(20); //limit value for query params, load more
63
+ const [offset, setOffset] = useState(0);//offset value for query params, load more
64
+ const [appliedFilters, setAppliedFilters] = useState(null);
65
+ const [incidentsListForLink, setIncidentsListForLink] = useState([]); // data for MCL
66
+ const [totalResultsForLink, setTotalResultsForLink] = useState(0);
67
+ const [readyQuery, setReadyQuery] = useState(null) // local query string
68
+ const [locationInputValue, setLocationInputValue] = useState('');
69
+ const [locationsVisibleCount, setLocationsVisibleCount] = useState(5);
70
+ const [locationsHasExpanded, setLocationsHasExpanded] = useState(false);
71
+ const [incidentTypeInputValue, setIncidentTypeInputValue] = useState('');
72
+ const [incidentTypesVisibleCount, setIncidentTypesVisibleCount] = useState(5);
73
+ const [incTypesHasExpanded, setIncTypesHasExpanded] = useState(false);
74
+ const [resetKey, setResetKey] = useState(0);
75
+ const [disableCreatedByTextField, setDisableCreatedByTextField] = useState(false);
76
+ const [disableWitnessedByTextField, setDisableWitnessedByTextField] = useState(false);
77
+ const [formSearchParams, setFormSearchParams] = useState({
78
+ searchType: 'keyword', // type of search (e.g. 'keyword', 'name-or-barcode')
79
+ term: '', // user input of search term
80
+ locationValue: [], // location filter set via Checkbox
81
+ incidentTypeId: [], // incident type filter set via Checkbox
82
+ witnessedBy: '', // search by who witnessed/staff involved in incident
83
+ createdBy: '', // search by who created the incident
84
+ startDate: '', // filter by incident start date
85
+ endDate: '', // filter by incident end date
86
+ currentTrespass: false, // filter by has current trespass
87
+ expiredTrespass: false, // filter by has expired trespass
88
+ timezone: '', // gets set if query with date range (no UI)
89
+ // includeSuppressed: false, // default show non-suppressed
90
+ // notIncludeSuppressed: true
91
+ staffSuppress: 'non' // legal values 'non'(default), 'suppressed', 'all'
92
+ });
93
+
94
+ const logSelection = (label, set, total) => {
95
+ const ids = [...set];
96
+
97
+ console.log(
98
+ `[${label}] ${ids.length}/${total} selected`,
99
+ ids.length === total ? 'ALL rows' : '',
100
+ ids.sort()
101
+ );
102
+ };
103
+
104
+ // useEffect(() => {
105
+ // logSelection('effect', ids, incidentsListForLink.length);
106
+ // }, [ids]);
107
+
108
+ const buildLocalQuery = ({
109
+ filters,
110
+ limit,
111
+ offset,
112
+ sortColumn,
113
+ sortDirection,
114
+ organizationTimezone,
115
+ }) => {
116
+ const cleaned = cleanFormValues(
117
+ filters,
118
+ organizationTimezone,
119
+ Intl.DateTimeFormat().resolvedOptions().timeZone
120
+ );
121
+
122
+ // if there's no sortColumn, pass empty strings so back end ignores sort.
123
+ return buildQueryString(
124
+ { ...cleaned, limit, offset },
125
+ sortColumn || '',
126
+ sortColumn ? sortDirection : '' // 'asc' | 'desc' or ''
127
+ );
128
+ };
129
+
130
+ useEffect(() => {
131
+ // Compare the *filter-only* canonical strings to avoid ref churn
132
+ const canonical = buildQueryString(
133
+ cleanFormValues(formSearchParams, organizationTimezone, Intl.DateTimeFormat().resolvedOptions().timeZone),
134
+ );
135
+ const canonicalApplied = appliedFilters
136
+ ? buildQueryString(
137
+ cleanFormValues(appliedFilters, organizationTimezone, Intl.DateTimeFormat().resolvedOptions().timeZone),
138
+ )
139
+ : '';
140
+
141
+ if (canonical !== canonicalApplied) {
142
+ // new filters -> reset to page 1
143
+ const nextFilters = formSearchParams;
144
+ const nextOffset = 0;
145
+
146
+ setAppliedFilters(nextFilters);
147
+ setOffset(nextOffset);
148
+
149
+ const qs = buildLocalQuery({
150
+ filters: nextFilters,
151
+ limit,
152
+ offset: nextOffset,
153
+ sortColumn,
154
+ sortDirection,
155
+ organizationTimezone,
156
+ });
157
+
158
+ setReadyQuery(qs);
159
+ }
160
+ // Only re-run when the toggled filter groups change:
161
+ // eslint-disable-next-line react-hooks/exhaustive-deps
162
+ }, [
163
+ formSearchParams.locationValue,
164
+ formSearchParams.incidentTypeId,
165
+ formSearchParams.currentTrespass,
166
+ formSearchParams.expiredTrespass,
167
+ formSearchParams.staffSuppress,
168
+ organizationTimezone,
169
+ ]);
170
+
171
+ const handleSubmit = (resetPaging = true, overrideForm = formSearchParams) => {
172
+ const nextFilters = overrideForm;
173
+ const nextOffset = resetPaging ? 0 : offset;
174
+
175
+ setAppliedFilters(nextFilters);
176
+ setOffset(nextOffset);
177
+
178
+ const qs = buildLocalQuery({
179
+ filters: nextFilters,
180
+ limit,
181
+ offset: nextOffset,
182
+ sortColumn,
183
+ sortDirection, // already 'asc' | 'desc'
184
+ organizationTimezone,
185
+ });
186
+
187
+ setReadyQuery(qs);
188
+ };
189
+
190
+ const handleSort = (_e, { name }) => {
191
+ const nextCol = name;
192
+ const nextDir = name === sortColumn && sortDirection === 'asc' ? 'desc' : 'asc';
193
+
194
+ setSortColumn(nextCol);
195
+ setSortDirection(nextDir);
196
+
197
+ const baseFilters = appliedFilters || formSearchParams;
198
+ const nextOffset = 0;
199
+ setOffset(nextOffset);
200
+
201
+ const qs = buildLocalQuery({
202
+ filters: baseFilters,
203
+ limit,
204
+ offset: nextOffset,
205
+ sortColumn: nextCol,
206
+ sortDirection: nextDir,
207
+ organizationTimezone,
208
+ });
209
+
210
+ setReadyQuery(qs);
211
+ };
212
+
213
+ const handleLoadMore = () => {
214
+ const nextOffset = incidentsListForLink.length; // append after current rows
215
+ setOffset(nextOffset);
216
+
217
+ const baseFilters = appliedFilters || formSearchParams;
218
+
219
+ const qs = buildLocalQuery({
220
+ filters: baseFilters,
221
+ limit,
222
+ offset: nextOffset,
223
+ sortColumn,
224
+ sortDirection,
225
+ organizationTimezone,
226
+ });
227
+
228
+ setReadyQuery(qs);
229
+ };
230
+
231
+
232
+ const handleResetAll = () => {
233
+ const blankFilters = {
234
+ searchType: formSearchParams.searchType,
235
+ term: '',
236
+ locationValue: [],
237
+ incidentTypeId: [],
238
+ witnessedBy: '',
239
+ createdBy: '',
240
+ startDate: '',
241
+ endDate: '',
242
+ timezone: '',
243
+ currentTrespass: false,
244
+ expiredTrespass: false,
245
+ staffSuppress: 'non',
246
+ };
247
+
248
+ // UI resets
249
+ setLocationInputValue('');
250
+ setIncidentTypeInputValue('');
251
+ setResetKey((k) => k + 1);
252
+ setSortColumn('');
253
+ setSortDirection('');
254
+ setFormSearchParams(blankFilters);
255
+
256
+ // Run a new baseline search (page 1)
257
+ // handleSubmit(true, blankFilters);
258
+ };
259
+
260
+ // produce canonical (sorted) string that contains only the filter params
261
+ // const buildFiltersOnlyQS = (source = formSearchParams) => {
262
+ // const cleaned = cleanFormValues(
263
+ // source,
264
+ // organizationTimezone,
265
+ // Intl.DateTimeFormat().resolvedOptions().timeZone
266
+ // );
267
+ // // drop paging, always restart at the first page
268
+ // const { limit: _1, offset: _0, ...filters } = cleaned;
269
+ // return buildQueryString(filters) // "locationValue=..." etc
270
+ // };
271
+
272
+ const handleClearSearchField = () => {
273
+ setFormSearchParams((prev) => {
274
+ return {
275
+ ...prev,
276
+ term: '',
277
+ };
278
+ });
279
+ };
280
+
281
+ const handleKeyDown = (event) => {
282
+ if (event.key === 'Enter') {
283
+ handleSubmit(true);
284
+ }
285
+ };
286
+
287
+ const handleParamsValueChange = (event) => {
288
+ const { name, value } = event.target;
289
+ setFormSearchParams((prev) => ({
290
+ ...prev,
291
+ [name]: value,
292
+ }));
293
+ };
294
+
295
+ const handleIndexChange = (event) => {
296
+ if (event.target.value === 'created-by') {
297
+ setDisableCreatedByTextField(true);
298
+ } else {
299
+ setDisableCreatedByTextField(false);
300
+ };
301
+ if (event.target.value === 'witnessed-by') {
302
+ setDisableWitnessedByTextField(true);
303
+ } else {
304
+ setDisableWitnessedByTextField(false);
305
+ };
306
+ setFormSearchParams((prev) => ({
307
+ ...prev,
308
+ searchType: event.target.value,
309
+ }));
310
+ };
311
+
312
+ const handleCreatedByChange = (event) => {
313
+ const { value } = event.target;
314
+ setFormSearchParams((prev) => {
315
+ const newState = { ...prev };
316
+ if (value) {
317
+ newState.createdBy = value;
318
+ } else {
319
+ delete newState.createdBy;
320
+ }
321
+ return newState;
322
+ });
323
+ };
324
+
325
+ const handleWitnessedByChange = (event) => {
326
+ const { value } = event.target;
327
+ setFormSearchParams((prev) => {
328
+ const newState = { ...prev };
329
+ if (value) {
330
+ newState.witnessedBy = value;
331
+ } else {
332
+ delete newState.witnessedBy;
333
+ }
334
+ return newState;
335
+ });
336
+ };
337
+
338
+ const handleLocationFilterChange = (value) => {
339
+ setFormSearchParams((prevParams) => {
340
+ const currentLocations = prevParams.locationValue;
341
+ const isChecked = currentLocations.includes(value);
342
+
343
+ // add/remove value
344
+ const updatedLocations = isChecked
345
+ ? currentLocations.filter((loc) => loc !== value)
346
+ : [...currentLocations, value];
347
+
348
+ return {
349
+ ...prevParams,
350
+ locationValue: updatedLocations
351
+ };
352
+ });
353
+ };
354
+
355
+ const handleIncidentTypeFilterChange = (value) => {
356
+ setFormSearchParams((prevParams) => {
357
+ const currentIncTypeIds = prevParams.incidentTypeId;
358
+ const isChecked = currentIncTypeIds.includes(value);
359
+
360
+ // add/remove value
361
+ const updatedIncTypeIds = isChecked
362
+ ? currentIncTypeIds.filter((loc) => loc !== value)
363
+ : [...currentIncTypeIds, value];
364
+
365
+ return {
366
+ ...prevParams,
367
+ incidentTypeId: updatedIncTypeIds
368
+ };
369
+ });
370
+ };
371
+
372
+ const handleTrespassFilterChange = (value) => {
373
+ setFormSearchParams((prev) => {
374
+ const params = { ...prev };
375
+ if (value === 'none') {
376
+ delete params.currentTrespass;
377
+ delete params.expiredTrespass;
378
+ };
379
+ if (value === 'currentTrespass') {
380
+ params.currentTrespass = true;
381
+ delete params.expiredTrespass;
382
+ };
383
+ if (value === 'expiredTrespass') {
384
+ params.expiredTrespass = true;
385
+ delete params.currentTrespass;
386
+ };
387
+ return params;
388
+ });
389
+ };
390
+
391
+ const handleSuppressedFilterChange = (event) => {
392
+ const { value } = event.target;
393
+ // console.log("value --> ", value);
394
+ setFormSearchParams((prev) => ({
395
+ ...prev,
396
+ staffSuppress: value
397
+ }));
398
+ };
399
+
400
+ const customCameraSvg = (props) => (
401
+ <svg
402
+ xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32" {...props}>
403
+ <g clipPath="url(#a)">
404
+ <path fill="#fff" d="M0 0h24v24H0z"/><path stroke="#000" strokeLinecap="round" strokeLinejoin="round" d="M3 8a1 1 0 0 1 1-1h4.5l1-3h5l1 3H20a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V8Z"/>
405
+ <circle cx="12" cy="13" r="3" stroke="#000" strokeLinejoin="round"/>
406
+ </g>
407
+ <defs>
408
+ <clipPath id="a">
409
+ <path fill="#fff" d="M0 0h24v24H0z"/></clipPath>
410
+ </defs>
411
+ </svg>
412
+ );
413
+
414
+ const inlineIconStyle = {
415
+ display: 'inline-flex',
416
+ alignItems: 'center',
417
+ verticalAlign: 'middle',
418
+ lineHeight: '1.2',
419
+ gap: '4px',
420
+ marginLeft: '8px'
421
+ };
422
+
423
+ const selectAllHeader = (
424
+ <Checkbox
425
+ aria-label={<FormattedMessage id="column-mapping.select-all-or-deselect-rows-aria-label" />}
426
+ checked={ids.size === incidentsListForLink.length}
427
+ indeterminate={
428
+ ids.size > 0 && ids.size < incidentsListForLink.length
429
+ }
430
+ onClick={(e) => {
431
+ e.stopPropagation();
432
+ ids.size === incidentsListForLink.length
433
+ ? setIds(new Set())
434
+ : setIds(new Set(incidentsListForLink.map(r => r.id)));
435
+ }}
436
+ />
437
+ );
438
+
439
+ const resultsFormatter = {
440
+ id: (item) => {
441
+ return (
442
+ <Checkbox
443
+ checked={ids.has(item.id)}
444
+ value={item.id}
445
+ onClick={(e) => {
446
+ e.stopPropagation();
447
+ toggleRowChecked(item.id)
448
+ }}
449
+ onKeyDown={(e) => {
450
+ if (e.key === ' ' || e.key === 'Enter') e.stopPropagation();
451
+ }}
452
+ />
453
+ )
454
+ },
455
+ customers: (item) => {
456
+ const isStaffSuppressed = item.staffSuppressed && item.staffSuppressed === true;
457
+ // if report has attachments of image or video, returns true
458
+ const hasImageOrVideo = item.attachments?.some((att) => {
459
+ return att.contentType.startsWith('image/') || att.contentType.startsWith('video/') || false
460
+ });
461
+ // if report has non trespass related document, returns true
462
+ const hasNonTrespassPDF = item.attachments?.some((att) => att.contentType === 'application/pdf' && !att.id?.toLowerCase()?.includes('trespass')) || false;
463
+
464
+ const customerNameList = item.customers?.map((cust, index) => {
465
+ const nameLabel = cust.customerNameLabel;
466
+ const custDescSnippet = cust.customerDescSnippet;
467
+ const trespassServed =
468
+ cust.trespass && cust.trespass.declarationOfService;
469
+ const endDateOfTrespass = cust.trespass?.endDateOfTrespass ?? null;
470
+ const isTrespassExpired =
471
+ endDateOfTrespass && new Date(endDateOfTrespass) < Date.now();
472
+
473
+ return (
474
+ <li key={index}>
475
+ {nameLabel ? nameLabel : custDescSnippet}
476
+
477
+ {/* render green check for has been served 'in hand' */}
478
+ {trespassServed && !isTrespassExpired ? (
479
+ <span style={{
480
+ ...inlineIconStyle,
481
+ color: 'green'
482
+ }}>
483
+ <Icon icon="check-circle"/>
484
+ </span>
485
+ ) : (
486
+ null
487
+ )}
488
+ {/* render X if currently trespassed, else trespass expired */}
489
+ {cust.trespass && !isTrespassExpired ? (
490
+ <span style={{
491
+ ...inlineIconStyle,
492
+ color: 'red'
493
+ }}>
494
+ <Icon
495
+ icon="times-circle-solid" />
496
+ {endDateOfTrespass ?
497
+ convertUTCISOToPrettyDate(endDateOfTrespass)
498
+ : <FormattedMessage id="results-pane.customers-formatter-trespass-no-end-date"/>}
499
+ </span>
500
+ ) : cust.trespass && isTrespassExpired ? (
501
+ <span style={{ marginLeft: '10px' }}>
502
+ <FormattedMessage id="results-pane.customers-formatter-trespass-expired"/>
503
+ </span>
504
+ ) : (
505
+ null
506
+ )}
507
+ {/* render icon if report has attachment(s) of image or video or non-trespass pdf*/}
508
+ {hasImageOrVideo || hasNonTrespassPDF ? (
509
+ <span style={{ ...inlineIconStyle, marginTop: '3px', }}>
510
+ <Icon
511
+ size='large'
512
+ iconClassName='cameraIcon'
513
+ icon={customCameraSvg}
514
+ aria-label='camera icon'
515
+ />
516
+ </span>
517
+ ) : (
518
+ null
519
+ )}
520
+ {isStaffSuppressed ? (
521
+ <span
522
+ style={{
523
+ display: 'inline-block',
524
+ marginBottom: '4px',
525
+ marginLeft: '3px',
526
+ verticalAlign: 'middle'
527
+ }}>
528
+ <Icon size='small' icon='exclamation-circle' status='warn'>
529
+ </Icon>
530
+ </span>)
531
+ : (
532
+ null
533
+ )}
534
+ </li>
535
+ );
536
+ });
537
+ return <ul style={{ margin: '0' }}>
538
+ {customerNameList?.length > 0 ?
539
+ customerNameList
540
+ : (
541
+ <li>
542
+ <FormattedMessage
543
+ id="results-pane.customers-formatter-no-associated-customers"
544
+ />
545
+ {hasImageOrVideo || hasNonTrespassPDF ? (
546
+ <span style={{ ...inlineIconStyle, marginTop: '3px', }}>
547
+ <Icon
548
+ size='large'
549
+ iconClassName='cameraIcon'
550
+ icon={customCameraSvg}
551
+ aria-label='camera icon'
552
+ />
553
+ </span>
554
+ ) : (
555
+ null
556
+ )}
557
+ {isStaffSuppressed ? (
558
+ <span
559
+ style={{
560
+ display: 'inline-block',
561
+ marginBottom: '2px',
562
+ marginLeft: '3px',
563
+ verticalAlign: 'middle'
564
+
565
+ }}>
566
+ <Icon size='small' icon='exclamation-circle' status='warn'>
567
+ </Icon>
568
+ </span>)
569
+ : (
570
+ null
571
+ )}
572
+ </li>
573
+ )
574
+ }
575
+ </ul>;
576
+ },
577
+ incidentLocation: (item) => {
578
+ return item.incidentLocationLabel ? item.incidentLocationLabel : item.incidentLocation
579
+ },
580
+ dateOfIncident: (item) => {
581
+ const readableDate = convertUTCISOToPrettyDate(item.dateTimeOfIncident);
582
+ return readableDate;
583
+ },
584
+ endDateOfTrespass: (item) => {
585
+ const readableDate = item.convertUTCISOToPrettyDate(endDateOfTrespass);
586
+ return readableDate;
587
+ },
588
+ incidentTypes: (item) => {
589
+ const typeList = item.incidentTypesLabels.map((type, index) => (
590
+ <li key={index} style={{ listStyleType: 'none', padding: '5px 0' }}>
591
+ {type}
592
+ </li>
593
+ ));
594
+
595
+ return <ul style={{ padding: '0', margin: '0' }}>{typeList}</ul>;
596
+ },
597
+ incidentWitnesses: (item) => {
598
+ const staffInvolvedNamesAssociatedKeys = item.incidentWitnesses?.map((wit) => {
599
+ const witnessNameLabel = wit.witnessNameLabel;
600
+ return (
601
+ <li key={wit.id} style={{ listStyleType: 'none', padding: '5px 0' }}>
602
+ {`${witnessNameLabel}`}
603
+ </li>
604
+ );
605
+ });
606
+
607
+ return <ul style={{ padding: '0', margin: '0' }}>
608
+ {staffInvolvedNamesAssociatedKeys}
609
+ </ul>
610
+ },
611
+ createdBy: (item) => {
612
+ return item.createdByLabel ? item.createdByLabel
613
+ : `${item.createdBy?.lastName}, ${item.createdBy?.firstName}`
614
+ },
615
+ trespassExpirationDates: (item) => {
616
+ const { customers = [] } = item;
617
+
618
+ if (customers.length === 0) {
619
+ return <FormattedMessage id="no-customers" />
620
+ }
621
+
622
+ const endDates = customers
623
+ .map(cust => cust.trespass?.endDateOfTrespass)
624
+ .filter(Boolean);
625
+
626
+ if (endDates.length === 0) {
627
+ return <FormattedMessage id="no-trespass" />;
628
+ }
629
+
630
+ return (
631
+ <ul style={{ paddingLeft: 20, margin: 0 }}>
632
+ {endDates.map((date, idx) => (
633
+ <li key={idx}>{convertUTCISOToPrettyDate(date)}</li>
634
+ ))}
635
+ </ul>
636
+ );
637
+ }
638
+ };
639
+
640
+ const incidentTypesItems = useMemo(() => {
641
+ const formattedIncTypes = incidentTypesNamesIdsList
642
+ ? incidentTypesNamesIdsList.map((incident) => ({
643
+ value: incident.id,
644
+ label: incident.title,
645
+ }))
646
+ : [{
647
+ value: '',
648
+ label: <FormattedMessage
649
+ id="search-pane.incTypesItems-label-no-loaded"
650
+ />}];
651
+ return formattedIncTypes;
652
+ }, [incidentTypesNamesIdsList]);
653
+
654
+ const locationItems = useMemo(() => {
655
+ const formattedLocations = locationsInService
656
+ ? locationsInService.map((loc) => ({
657
+ value: loc.id,
658
+ label: loc.location,
659
+ }))
660
+ : [{
661
+ value: '',
662
+ label: <FormattedMessage
663
+ id="search-pane.locationItems-label-no-loaded"
664
+ />}];
665
+ return formattedLocations;
666
+ }, [locationsInService]);
667
+
668
+
669
+ // START locations help
670
+ const allFilteredLocations = locationItems.filter(item => item.label.toLowerCase().includes(locationInputValue.toLowerCase()));
671
+ const filteredLocations = allFilteredLocations.slice(0, locationsVisibleCount);
672
+ const handleMoreLocationsClick = () => {
673
+ setLocationsVisibleCount((prevCount) => {
674
+ return Math.min(prevCount + 5, allFilteredLocations.length)
675
+ });
676
+ };
677
+
678
+ const loadMoreLocations = () => {
679
+ handleMoreLocationsClick();
680
+ if (!locationsHasExpanded) {
681
+ setLocationsHasExpanded(true);
682
+ };
683
+ };
684
+
685
+ const locationsContainerStyle = {
686
+ maxHeight: locationsHasExpanded ? '175px' : '125px',
687
+ overflowX: 'clip',
688
+ overflowY: 'auto',
689
+ marginTop: '8px'
690
+ };
691
+ // END locations help
692
+
693
+ // START incident types help
694
+ const allFilteredIncidentTypes = incidentTypesItems.filter(item => item.label.toLowerCase().includes(incidentTypeInputValue.toLowerCase()));
695
+ const filteredIncidentTypes = allFilteredIncidentTypes.slice(0, incidentTypesVisibleCount);
696
+ const handleMoreIncidentTypesClick = () => {
697
+ setIncidentTypesVisibleCount((prevCount) => {
698
+ return Math.min(prevCount + 5, allFilteredIncidentTypes.length)
699
+ });
700
+ };
701
+
702
+ const loadMoreIncTypes = () => {
703
+ handleMoreIncidentTypesClick();
704
+ if (!incTypesHasExpanded) {
705
+ setIncTypesHasExpanded(true);
706
+ };
707
+ };
708
+
709
+ const incTypesContainerStyle = {
710
+ maxHeight: incTypesHasExpanded ? '175px' : '125px',
711
+ overflowX: 'clip',
712
+ overflowY: 'auto',
713
+ marginTop: '8px'
714
+ };
715
+ // END incident types help
716
+
717
+ const searchableIndexes = [
718
+ {
719
+ label: intl.formatMessage({
720
+ id: "search-pane.searchableIndex-label-keyword"
721
+ }),
722
+ value: 'keyword'
723
+ },
724
+ {
725
+ label: intl.formatMessage({
726
+ id: "search-pane.searchableIndex-label-name-or-barcode"
727
+ }),
728
+ value: 'name-or-barcode'
729
+ },
730
+ {
731
+ label: intl.formatMessage({
732
+ id: "search-pane.searchableIndex-label-customer-desc"
733
+ }),
734
+ value: 'customer-desc'
735
+ },
736
+ {
737
+ label: intl.formatMessage({
738
+ id: "search-pane.searchableIndex-label-witnessed-by"
739
+ }),
740
+ value: 'witnessed-by'
741
+ },
742
+ {
743
+ label: intl.formatMessage({
744
+ id: "search-pane.searchableIndex-label-created-by"
745
+ }),
746
+ value: 'created-by'
747
+ }
748
+ ];
749
+
750
+ const columnWidths = {
751
+ customers: '375px',
752
+ incidentLocation: '175px',
753
+ dateOfIncident: '110px',
754
+ incidentTypes: '175px'
755
+ };
756
+
757
+ const resultCount = intl.formatMessage(
758
+ { id: `results-pane.paneSubTitle` },
759
+ { count: totalResultsForLink }
760
+ );
761
+
762
+ const fixedColumns = ['id', 'customers'];
763
+ const toggleableColumns = useMemo(() => [
764
+ 'incidentLocation',
765
+ 'dateOfIncident',
766
+ 'incidentTypes',
767
+ 'incidentWitnesses',
768
+ 'createdBy',
769
+ 'trespassExpirationDates'
770
+ ], []);
771
+ const allColumns = [...fixedColumns, ...toggleableColumns];
772
+ const sortableFields = [...allColumns];
773
+ const columnLabels = {
774
+ customers: <FormattedMessage id="column-mapping.name" />,
775
+ incidentLocation: <FormattedMessage id="column-mapping.incidentLocation" />,
776
+ dateOfIncident: <FormattedMessage id="column-mapping.dateOfIncident" />,
777
+ incidentTypes: <FormattedMessage id="column-mapping.incidentTypes" />,
778
+ incidentWitnesses: <FormattedMessage id="column-mapping.witnessedBy" />,
779
+ createdBy: <FormattedMessage id="column-mapping.createdBy" />,
780
+ trespassExpirationDates: <FormattedMessage id="column-mapping.trespassExpirationDates" />
781
+ };
782
+
783
+ const [visibleToggleable, toggleColumn] = usePersistedColModalLink(toggleableColumns);
784
+
785
+ const visibleColumns = [...fixedColumns, ...visibleToggleable];
786
+
787
+ const actionMenu = useCallback(({ onToggle }) => (
788
+ <div
789
+ onMouseDown={(e) => e.stopPropagation()}
790
+ onClick={(e) => e.stopPropagation()}
791
+ onKeyDown={(e) => e.stopPropagation()}
792
+ style={{ padding: '0.5rem 0.75rem', maxWidth: 320 }}
793
+ >
794
+ <ColumnChooser
795
+ possibleColumns={toggleableColumns}
796
+ visibleColumns={visibleToggleable}
797
+ toggleColumn={toggleColumn}
798
+ columnLabels={columnLabels}
799
+ />
800
+ </div>
801
+ ));
802
+
803
+ const renderHeader = (renderProps) => (
804
+ <PaneHeader
805
+ {...renderProps}
806
+ paneTitle={<FormattedMessage id="results-pane.paneTitle" />}
807
+ paneSub={resultCount}
808
+ actionMenu={actionMenu}
809
+ />
810
+ );
811
+
812
+ const footer = (
813
+ <ModalFooter>
814
+ <Button
815
+ id="close-continue-button"
816
+ onClick={handleCloseModalLinkIncident}
817
+ buttonStyle="primary"
818
+ marginBottom0
819
+ >
820
+ <FormattedMessage id="close-continue-button" />
821
+ </Button>
822
+ </ModalFooter>
823
+ );
824
+
825
+ return (
826
+ <Modal
827
+ enforceFocus={false}
828
+ style={{
829
+ minHeight: '550px',
830
+ height: '80%', // allows modal to grow/shrink based on content
831
+ maxHeight: '600vh',
832
+ maxWidth: '600vw', // modal width responsive to viewport width
833
+ width: '70%' // modal width adjusts based on content and window size
834
+ }}
835
+ open
836
+ dismissible
837
+ closeOnBackgroundClick
838
+ label={<FormattedMessage id="search-pane.paneTitle" />}
839
+ size="large"
840
+ onClose={handleCloseModalLinkIncident}
841
+ footer={footer}
842
+ contentClass={css.modalContent}
843
+ >
844
+ <div className={css.modalBody}>
845
+ <Paneset style={{ height: '100%', flexGrow: 1 }}>
846
+ <Pane
847
+ paneTitle={<FormattedMessage id="search-pane.paneTitle" />}
848
+ defaultWidth="25%"
849
+ {...props}
850
+ >
851
+ <div className={css.leftPaneScroll}>
852
+
853
+ {readyQuery &&
854
+ <GetListDQLinkIncident
855
+ queryStringLinkIncident={readyQuery}
856
+ offset={offset}
857
+ setIncidentsListForLink={setIncidentsListForLink}
858
+ setTotalResultsForLink={setTotalResultsForLink}
859
+ />}
860
+
861
+ <GetIncidentTypesDetails
862
+ context='incidents'
863
+ />
864
+ <GetLocationsInService />
865
+ <GetLocations />
866
+ <GetOrgLocaleSettings />
867
+
868
+ <Row style={{ marginTop: '35px' }}>
869
+ <Col xs={10}>
870
+ <Headline
871
+ size="large"
872
+ margin="medium"
873
+ tag="h2"
874
+ id="searchField-label"
875
+ >
876
+ <FormattedMessage id="search-pane.searchField-h2-label" />
877
+ </Headline>
878
+ </Col>
879
+ </Row>
880
+
881
+ <Row>
882
+ <Col xs={12}>
883
+ <SearchField
884
+ aria-labelledby="searchField-label"
885
+ key={resetKey}
886
+ onChange={handleParamsValueChange}
887
+ name="term"
888
+ value={formSearchParams.term}
889
+ searchableIndexes={searchableIndexes}
890
+ onChangeIndex={handleIndexChange}
891
+ onClear={handleClearSearchField}
892
+ onKeyDown={handleKeyDown}
893
+ />
894
+
895
+ <Row style={{ marginTop: '45px' }}>
896
+ <Col xs={10}>
897
+ <Headline size="large" margin="medium" tag="h2">
898
+ <FormattedMessage id="search-pane.filters-h2-label" />
899
+ </Headline>
900
+ </Col>
901
+ </Row>
902
+
903
+ <Accordion label={
904
+ <FormattedMessage id="search-pane.accordion-label-location" />
905
+ }>
906
+ <Row>
907
+ <Col xs={10}>
908
+ <AutoSuggest
909
+ value={locationInputValue}
910
+ items={[]}
911
+ onChange={setLocationInputValue}
912
+ menuStyle={{ display: 'none' }}
913
+ renderValue={(val) => val || ''} //render item in input field
914
+ />
915
+ </Col>
916
+ </Row>
917
+ <Row>
918
+ <Col xs={12} style={{ marginLeft: '10px' }}>
919
+ <div style={locationsContainerStyle}>
920
+ {filteredLocations.map((item) => (
921
+ <Checkbox
922
+ key={item.value}
923
+ label={item.label}
924
+ value={item.value}
925
+ checked={formSearchParams.locationValue.includes(item.value)}
926
+ onChange={() => handleLocationFilterChange(item.value)}
927
+ />
928
+ ))}
929
+ </div>
930
+ <div style={{ marginTop: '2px' }}>
931
+ {locationsVisibleCount < allFilteredLocations.length && (
932
+ <Button
933
+ onClick={loadMoreLocations}
934
+ >
935
+ <FormattedMessage id="more-button" />
936
+ </Button>
937
+ )}
938
+ </div>
939
+ </Col>
940
+ </Row>
941
+ </Accordion>
942
+
943
+ <Accordion label={
944
+ <FormattedMessage id="search-pane.accordion-label-incident-types" />
945
+ }>
946
+ <Row>
947
+ <Col xs={10}>
948
+ <AutoSuggest
949
+ value={incidentTypeInputValue}
950
+ items={[]}
951
+ onChange={setIncidentTypeInputValue}
952
+ menuStyle={{ display: 'none' }}
953
+ renderValue={(val) => val || ''} //render item in input field
954
+ />
955
+ </Col>
956
+ </Row>
957
+ <Row>
958
+ <Col xs={12} style={{ marginLeft: '10px' }}>
959
+ <div style={incTypesContainerStyle}>
960
+ {filteredIncidentTypes.map((item) => (
961
+ <Checkbox
962
+ value={item.value}
963
+ key={item.value}
964
+ label={item.label}
965
+ checked={formSearchParams.incidentTypeId.includes(item.value)}
966
+ onChange={() => handleIncidentTypeFilterChange(item.value)}
967
+ />
968
+ ))}
969
+ </div>
970
+ <div style={{ marginTop: '2px' }}>
971
+ {incidentTypesVisibleCount < allFilteredIncidentTypes.length && (
972
+ <Button
973
+ onClick={loadMoreIncTypes}
974
+ >
975
+ <FormattedMessage id="more-button" />
976
+ </Button>
977
+ )}
978
+ </div>
979
+ </Col>
980
+ </Row>
981
+ </Accordion>
982
+ <hr/>
983
+
984
+ <Row style={{ marginTop: '25px'}}>
985
+ <Col xs={8}>
986
+ <TextField
987
+ disabled={disableWitnessedByTextField}
988
+ value={formSearchParams.witnessedBy || ''}
989
+ label={
990
+ <FormattedMessage id= "search-pane.witnessed-by-text-field-label"/>
991
+ }
992
+ onChange={handleWitnessedByChange}
993
+ />
994
+ </Col>
995
+ </Row>
996
+
997
+ <Row>
998
+ <Col xs={8}>
999
+ <TextField
1000
+ disabled={disableCreatedByTextField}
1001
+ value={formSearchParams.createdBy || ''}
1002
+ label={
1003
+ <FormattedMessage id="search-pane.created-by-text-field-label" />
1004
+ }
1005
+ onChange={handleCreatedByChange}
1006
+ />
1007
+ </Col>
1008
+ </Row>
1009
+ </Col>
1010
+ </Row>
1011
+
1012
+ <Row>
1013
+ <Col xs={10} style={{ marginTop: '12px' }}>
1014
+ <Datepicker
1015
+ label={
1016
+ <FormattedMessage id="search-pane.date-picker-from-label" />
1017
+ }
1018
+ name="startDate"
1019
+ value={formSearchParams.startDate || ''}
1020
+ onChange={handleParamsValueChange}
1021
+ />
1022
+ <Datepicker
1023
+ label={<FormattedMessage id="search-pane.date-picker-to-label" />}
1024
+ name="endDate"
1025
+ value={formSearchParams.endDate || ''}
1026
+ onChange={handleParamsValueChange}
1027
+ />
1028
+ </Col>
1029
+ </Row>
1030
+
1031
+ <Row>
1032
+ <Col xs={12}>
1033
+ <Headline size="large" tag="h2" style={{ marginTop: '15px'}}>
1034
+ <FormattedMessage id="search-pane.trespass-status-label" />
1035
+ </Headline>
1036
+ </Col>
1037
+ </Row>
1038
+
1039
+ <Row style={{ marginTop: '-15px'}}>
1040
+ <Col xs={12}>
1041
+ <RadioButton
1042
+ label={<FormattedMessage
1043
+ id="search-pane.radio-button-all-statuses-label"
1044
+ />}
1045
+ checked={!formSearchParams.currentTrespass && !formSearchParams.expiredTrespass}
1046
+ onChange={() => handleTrespassFilterChange('none')}
1047
+ />
1048
+ </Col>
1049
+ </Row>
1050
+
1051
+ <Row>
1052
+ <Col xs={12}>
1053
+ <RadioButton
1054
+ label={<FormattedMessage
1055
+ id="search-pane.radio-button-current-trespass-label"
1056
+ />}
1057
+ checked={formSearchParams.currentTrespass === true}
1058
+ onChange={() => handleTrespassFilterChange('currentTrespass')}
1059
+ />
1060
+ </Col>
1061
+ </Row>
1062
+
1063
+ <Row>
1064
+ <Col xs={12}>
1065
+ <RadioButton
1066
+ label={<FormattedMessage
1067
+ id="search-pane.radio-button-expired-trespass-label"
1068
+ />}
1069
+ checked={formSearchParams.expiredTrespass === true}
1070
+ onChange={() => handleTrespassFilterChange('expiredTrespass')}
1071
+ />
1072
+ </Col>
1073
+ </Row>
1074
+
1075
+ <Row style={{ marginTop: '20px' }}>
1076
+ <Col xs={12}>
1077
+ <Accordion
1078
+ closedByDefault
1079
+ label={<FormattedMessage
1080
+ id="search-pane.accordion-label-staff-suppressed"
1081
+ />}>
1082
+ <Row>
1083
+ <Col xs={12}>
1084
+ <RadioButton
1085
+ name='staffSuppress'
1086
+ value='non'
1087
+ checked={formSearchParams.staffSuppress === 'non'}
1088
+ label={<FormattedMessage
1089
+ id="search-pane.radio-button-suppressed-non-label"
1090
+ />}
1091
+ onChange={handleSuppressedFilterChange}
1092
+ />
1093
+ </Col>
1094
+ </Row>
1095
+ <Row>
1096
+ <Col xs={12}>
1097
+ <RadioButton
1098
+ name='staffSuppress'
1099
+ value='suppressed'
1100
+ checked={formSearchParams.staffSuppress === 'suppressed'}
1101
+ label={<FormattedMessage
1102
+ id="search-pane.radio-button-suppressed-suppressed-label"
1103
+ />}
1104
+ onChange={handleSuppressedFilterChange}
1105
+ />
1106
+ </Col>
1107
+ </Row>
1108
+ <Row>
1109
+ <Col xs={12}>
1110
+ <RadioButton
1111
+ name='staffSuppress'
1112
+ value='all'
1113
+ checked={formSearchParams.staffSuppress === 'all'}
1114
+ label={<FormattedMessage
1115
+ id="search-pane.radio-button-suppressed-all-label"
1116
+ />}
1117
+ onChange={handleSuppressedFilterChange}
1118
+ />
1119
+ </Col>
1120
+ </Row>
1121
+ </Accordion>
1122
+ </Col>
1123
+ </Row>
1124
+
1125
+ <Row>
1126
+ <Col xs={12}>
1127
+ <Button
1128
+ fullWidth
1129
+ style={{ marginTop: '23px' }}
1130
+ buttonStyle="primary"
1131
+ onClick={() => handleSubmit(true)}
1132
+ >
1133
+ <FormattedMessage id="search-button" />
1134
+ </Button>
1135
+ </Col>
1136
+ </Row>
1137
+
1138
+ <Row>
1139
+ <Col xs={12}>
1140
+ <Button
1141
+ fullWidth
1142
+ style={{ backgroundColor: 'rgb(222, 221, 217)' }}
1143
+ buttonStyle="disabled"
1144
+ onClick={handleResetAll}
1145
+ >
1146
+ <Icon icon="times-circle-solid">
1147
+ <FormattedMessage id="search-pane.reset-all-button" />
1148
+ </Icon>
1149
+ </Button>
1150
+ </Col>
1151
+ </Row>
1152
+ </div>
1153
+ </Pane>
1154
+
1155
+ { isLoadingSearch ? (
1156
+ <LoadingPane defaultWidth="fill" paneTitle="Loading results..." />
1157
+ ) : <Pane
1158
+ paneTitle={<FormattedMessage id="results-pane.paneTitle"/>}
1159
+ id="results-pane"
1160
+ defaultWidth="75%"
1161
+ {...props}
1162
+ renderHeader={renderHeader}
1163
+ >
1164
+
1165
+ <div className={css.mclContainer}>
1166
+ <MultiColumnList
1167
+ autosize
1168
+ virtualize
1169
+ showSortIndicator
1170
+ sortableFields={sortableFields}
1171
+ onHeaderClick={handleSort}
1172
+ sortedColumn={sortColumn}
1173
+ sortDirection={sortDirection === 'desc' ? 'descending' : 'ascending'}
1174
+ contentData={incidentsListForLink}
1175
+ pageAmount={limit}
1176
+ totalCount={totalResultsForLink}
1177
+ pagingType='click'
1178
+ pagingOffset={incidentsListForLink.length}
1179
+ pagingCanGoNext={incidentsListForLink.length < totalResultsForLink}
1180
+ pagingCanGoPrevious={false}
1181
+ onNeedMoreData={() => handleLoadMore()}
1182
+ formatter={resultsFormatter}
1183
+ visibleColumns={visibleColumns}
1184
+ columnMapping={{
1185
+ id: selectAllHeader,
1186
+ customers: <FormattedMessage id="column-mapping.name" />,
1187
+ incidentLocation:
1188
+ <FormattedMessage id="column-mapping.incidentLocation" />
1189
+ ,
1190
+ dateOfIncident:
1191
+ <FormattedMessage id="column-mapping.dateOfIncident" />
1192
+ ,
1193
+ incidentTypes:
1194
+ <FormattedMessage id="column-mapping.incidentTypes" />
1195
+ ,
1196
+ incidentWitnesses:
1197
+ <FormattedMessage id="column-mapping.witnessedBy" />,
1198
+ createdBy:
1199
+ <FormattedMessage id="column-mapping.createdBy" />,
1200
+ trespassExpirationDates:
1201
+ <FormattedMessage id="column-mapping.trespassExpirationDates" />,
1202
+ }}
1203
+ columnWidths={columnWidths}
1204
+ />
1205
+ </div>
1206
+ </Pane>}
1207
+ </Paneset>
1208
+ </div>
1209
+ </Modal>
1210
+ );
1211
+ };
1212
+
1213
+ export default ModalLinkIncident;