@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,810 @@
1
+ import React, { useState, useEffect, useMemo, useRef } from 'react';
2
+ import { useLocation, useHistory } from 'react-router-dom';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { useIntl } from 'react-intl';
5
+ import {
6
+ Accordion,
7
+ AutoSuggest,
8
+ Checkbox,
9
+ Paneset,
10
+ Pane,
11
+ SearchField,
12
+ Button,
13
+ Headline,
14
+ Datepicker,
15
+ Row,
16
+ Col,
17
+ Icon,
18
+ TextField,
19
+ RadioButton
20
+ } from '@folio/stripes/components';
21
+ import GetLocationsInService from '../../settings/GetLocationsInService';
22
+ import GetListDynamicQuery from './GetListDynamicQuery';
23
+ import GetLocations from './GetLocations';
24
+ import GetIncidentTypesDetails from '../../settings/GetIncidentTypesDetails';
25
+ import buildQueryString from './helpers/buildQueryString.js';
26
+ import cleanFormValues from './helpers/cleanFormValues.js';
27
+ import decodeParamsToForm from './helpers/decodeParamsToForm.js';
28
+ import ResultsPane from './ResultsPane';
29
+ import DetailsPane from './DetailsPane';
30
+ import EditPane from './EditPane';
31
+ import CreatePane from './CreatePane';
32
+ import GetOrgLocaleSettings from './GetOrgLocaleSettings.js';
33
+ import usePersistedSort from './usePersistedSort.js';
34
+ import { useIncidents } from '../../contexts/IncidentContext';
35
+
36
+ const IncidentsPaneset = ({ ...props }) => {
37
+ const intl = useIntl();
38
+ const {
39
+ isDetailsPaneOpen,
40
+ isEditPaneOpen,
41
+ isCreatePaneOpen,
42
+ incidentTypesNamesIdsList,
43
+ locationsInService,
44
+ setQueryString,
45
+ queryString,
46
+ limit, setLimit,
47
+ offset, setOffset,
48
+ organizationTimezone,
49
+ appliedSearchParams, setAppliedSearchParams
50
+ } = useIncidents();
51
+
52
+ const {
53
+ sortColumn,
54
+ sortDirection,
55
+ setSortColumn,
56
+ setSortDirection
57
+ } = usePersistedSort();
58
+
59
+ const [locationInputValue, setLocationInputValue] = useState('');
60
+ const [locationsVisibleCount, setLocationsVisibleCount] = useState(5);
61
+ const [locationsHasExpanded, setLocationsHasExpanded] = useState(false);
62
+ const [incidentTypeInputValue, setIncidentTypeInputValue] = useState('');
63
+ const [incidentTypesVisibleCount, setIncidentTypesVisibleCount] = useState(5);
64
+ const [incTypesHasExpanded, setIncTypesHasExpanded] = useState(false);
65
+ const [resetKey, setResetKey] = useState(0);
66
+ const [disableCreatedByTextField, setDisableCreatedByTextField] = useState(false);
67
+ const [disableWitnessedByTextField, setDisableWitnessedByTextField] = useState(false);
68
+ const [formSearchParams, setFormSearchParams] = useState({
69
+ searchType: 'keyword', // type of search (e.g. 'keyword', 'name-or-barcode')
70
+ term: '', // user input of search term
71
+ locationValue: [], // location filter set via Checkbox
72
+ incidentTypeId: [], // incident type filter set via Checkbox
73
+ witnessedBy: '', // search by who witnessed/staff involved in incident
74
+ createdBy: '', // search by who created the incident
75
+ startDate: '', // filter by incident start date
76
+ endDate: '', // filter by incident end date
77
+ currentTrespass: false, // filter by has current trespass
78
+ expiredTrespass: false, // filter by has expired trespass
79
+ timezone: '', // gets set if query with date range (no UI)
80
+ // includeSuppressed: false, // default show non-suppressed
81
+ // notIncludeSuppressed: true
82
+ staffSuppress: 'non' // legal values 'non'(default), 'suppressed', 'all'
83
+ });
84
+
85
+ const history = useHistory();
86
+ const location = useLocation();
87
+ const firstRunRef = useRef(true); // ignore the very first render after mount
88
+ /*
89
+ skipNextSubmitRef: mutable flag
90
+ one shot flag that suppresses the filters watcher.
91
+
92
+ When we programmatically copy values from the URL into local state (location.search -->
93
+ setFormSearchParams, setSortColumn, ...) we do not want the filters watcher useEffect to treat that as a 'user change' and push a second, identical URL or fire a duplicate search.
94
+
95
+ sequence:
96
+ 1. URL sync effect sets skipNextSubmitRef.current = true
97
+ 2. It then calls the various state setters
98
+ 3. filters watcher runs, sees the flag, exists immediately, and
99
+ resets skipNextSubmitREf.current = false
100
+ 4. Future real user edits run the watcher normally
101
+ */
102
+ const skipNextSubmitRef = useRef(false);
103
+
104
+ // URL-sync effect
105
+ useEffect(() => {
106
+ if (!location.pathname.startsWith('/incidents')) {
107
+ return;
108
+ }
109
+ // initial on render list route, inject defaults once
110
+ if (location.pathname === '/incidents' && location.search === '') {
111
+ history.replace(`/incidents?limit=20&offset=0`);
112
+ return;
113
+ };
114
+ // any route with no search params (Details/Edit) - keep current
115
+ if(location.pathname !== '/incidents') {
116
+ return; // do not touch limit/offset/queryString
117
+ }
118
+ const params = new URLSearchParams(location.search);
119
+ const limit = +(params.get('limit') || 20);
120
+ const offset = +(params.get('offset') || 0);
121
+ const sort = params.get('sort') ?? '';
122
+ const dir = params.get('dir') ?? 'asc';
123
+ const filterObj = Object.fromEntries(params);
124
+
125
+ skipNextSubmitRef.current = true; // tell watcher ignore 1st pass
126
+
127
+ setSortColumn(sort);
128
+ setSortDirection(dir);
129
+ setLimit(limit);
130
+ setOffset(offset);
131
+ setAppliedSearchParams(filterObj);
132
+ setQueryString(params.toString());
133
+ setFormSearchParams(decodeParamsToForm(filterObj));
134
+ if (firstRunRef.current) firstRunRef.current = false;
135
+ }, [location.search, history]);
136
+
137
+ const handlePagination = (_askAmount, newOffset) => {
138
+ if (newOffset < 0 || newOffset === offset) return;
139
+
140
+ const qs = buildQueryString({
141
+ ...appliedSearchParams,
142
+ limit,
143
+ offset: newOffset,
144
+ },
145
+ sortColumn,
146
+ sortDirection
147
+ );
148
+
149
+ history.push(`/incidents?${qs}`);
150
+ };
151
+
152
+ const handleSubmit = (
153
+ resetPaging = true,
154
+ overrideForm = formSearchParams // default = current state
155
+ ) => {
156
+ const cleaned = cleanFormValues(
157
+ overrideForm,
158
+ // formSearchParams,
159
+ organizationTimezone,
160
+ Intl.DateTimeFormat().resolvedOptions().timeZone
161
+ );
162
+ // new searches reset paging
163
+ const qs = buildQueryString({
164
+ ...cleaned,
165
+ limit,
166
+ offset: resetPaging ? 0 : offset
167
+ },
168
+ sortColumn,
169
+ sortColumn !== '' ? (sortDirection === 'descending' ? 'desc' : 'asc') : ''
170
+ // sortDirection === 'descending' ? 'desc' : 'asc'
171
+ );
172
+
173
+ // console.log("qs --> ", JSON.stringify(qs, null, 2))
174
+ // URL changes trigger the [search] useEffect
175
+ history.push(`/incidents?${qs}`)
176
+ };
177
+
178
+ // produce canonical (sorted) string that contains only the filter params
179
+ const buildFiltersOnlyQS = (source = formSearchParams) => {
180
+ const cleaned = cleanFormValues(
181
+ source,
182
+ organizationTimezone,
183
+ Intl.DateTimeFormat().resolvedOptions().timeZone
184
+ );
185
+ // drop paging, always restart at the first page
186
+ const { limit: _1, offset: _0, ...filters } = cleaned;
187
+ return buildQueryString(filters) // "locationValue=..." etc
188
+ };
189
+
190
+ // handle run new search when filters are used
191
+ useEffect(() => {
192
+ if (firstRunRef.current) return;
193
+ if (skipNextSubmitRef.current) {
194
+ skipNextSubmitRef.current = false;
195
+ return;
196
+ }
197
+
198
+ const liveQS = buildFiltersOnlyQS(); // UI
199
+ const appliedQS = buildFiltersOnlyQS(
200
+ decodeParamsToForm(appliedSearchParams || {}) // URL
201
+ );
202
+
203
+ // compare the new string to the one derived from
204
+ // appliedSearchParams to know toggle
205
+ // intentionally ignore limit, offset so paging doesn't retrigger
206
+ // this effect
207
+ if (liveQS !== appliedQS) {
208
+ handleSubmit(true); // run search reset paging
209
+ };
210
+ }, [
211
+ formSearchParams.locationValue,
212
+ formSearchParams.incidentTypeId,
213
+ formSearchParams.currentTrespass,
214
+ formSearchParams.expiredTrespass,
215
+ formSearchParams.staffSuppress
216
+ ]);
217
+
218
+ // reset to on-app render baseline results (top n, most recent)
219
+ const handleResetAll = () => {
220
+ const blankFilters = {
221
+ searchType: formSearchParams.searchType,
222
+ term: '',
223
+ locationValue: [],
224
+ incidentTypeId: [],
225
+ witnessedBy: '',
226
+ createdBy: '',
227
+ startDate: '',
228
+ endDate: '',
229
+ timezone: '',
230
+ currentTrespass: false,
231
+ expiredTrespass: false
232
+ };
233
+ // build cleaned query str w/ no sort/dir
234
+ const cleaned = cleanFormValues(
235
+ blankFilters,
236
+ organizationTimezone,
237
+ Intl.DateTimeFormat().resolvedOptions().timeZone
238
+ );
239
+
240
+ const qs = buildQueryString(
241
+ { ...cleaned, limit, offset: 0 }, // reset paging
242
+ '', // no sort
243
+ '' // no dir
244
+ )
245
+ history.push(`/incidents?${qs}`);
246
+
247
+ setFormSearchParams(blankFilters);
248
+ setAppliedSearchParams(''); // clear cached URL filters
249
+ setLocationInputValue('');
250
+ setIncidentTypeInputValue('');
251
+ // handles reset <Select/> 'searchableIndexes' default value to 'keyword'
252
+ setResetKey((prevKey) => prevKey + 1);
253
+ setSortColumn('');
254
+ setSortDirection('');
255
+ };
256
+
257
+ const handleClearSearchField = () => {
258
+ setFormSearchParams((prev) => {
259
+ return {
260
+ ...prev,
261
+ term: '',
262
+ };
263
+ });
264
+ };
265
+
266
+ const handleKeyDown = (event) => {
267
+ if (event.key === 'Enter') {
268
+ handleSubmit(true);
269
+ }
270
+ };
271
+
272
+ const incidentTypesItems = useMemo(() => {
273
+ const formattedIncTypes = incidentTypesNamesIdsList
274
+ ? incidentTypesNamesIdsList.map((incident) => ({
275
+ value: incident.id,
276
+ label: incident.title,
277
+ }))
278
+ : [{
279
+ value: '',
280
+ label: <FormattedMessage
281
+ id="search-pane.incTypesItems-label-no-loaded"
282
+ />}];
283
+ return formattedIncTypes;
284
+ }, [incidentTypesNamesIdsList]);
285
+
286
+ const locationItems = useMemo(() => {
287
+ const formattedLocations = locationsInService
288
+ ? locationsInService.map((loc) => ({
289
+ value: loc.id,
290
+ label: loc.location,
291
+ }))
292
+ : [{
293
+ value: '',
294
+ label: <FormattedMessage
295
+ id="search-pane.locationItems-label-no-loaded"
296
+ />}];
297
+ return formattedLocations;
298
+ }, [locationsInService]);
299
+
300
+ const handleParamsValueChange = (event) => {
301
+ const { name, value } = event.target;
302
+ setFormSearchParams((prev) => ({
303
+ ...prev,
304
+ [name]: value,
305
+ }));
306
+ };
307
+
308
+ const handleIndexChange = (event) => {
309
+ if (event.target.value === 'created-by') {
310
+ setDisableCreatedByTextField(true);
311
+ } else {
312
+ setDisableCreatedByTextField(false);
313
+ };
314
+ if (event.target.value === 'witnessed-by') {
315
+ setDisableWitnessedByTextField(true);
316
+ } else {
317
+ setDisableWitnessedByTextField(false);
318
+ };
319
+ setFormSearchParams((prev) => ({
320
+ ...prev,
321
+ searchType: event.target.value,
322
+ }));
323
+ };
324
+
325
+ const handleCreatedByChange = (event) => {
326
+ const { value } = event.target;
327
+ setFormSearchParams((prev) => {
328
+ const newState = { ...prev };
329
+ if (value) {
330
+ newState.createdBy = value;
331
+ } else {
332
+ delete newState.createdBy;
333
+ }
334
+ return newState;
335
+ });
336
+ };
337
+
338
+ const handleWitnessedByChange = (event) => {
339
+ const { value } = event.target;
340
+ setFormSearchParams((prev) => {
341
+ const newState = { ...prev };
342
+ if (value) {
343
+ newState.witnessedBy = value;
344
+ } else {
345
+ delete newState.witnessedBy;
346
+ }
347
+ return newState;
348
+ });
349
+ };
350
+
351
+ const handleLocationFilterChange = (value) => {
352
+ setFormSearchParams((prevParams) => {
353
+ const currentLocations = prevParams.locationValue;
354
+ const isChecked = currentLocations.includes(value);
355
+
356
+ // add/remove value
357
+ const updatedLocations = isChecked
358
+ ? currentLocations.filter((loc) => loc !== value)
359
+ : [...currentLocations, value];
360
+
361
+ return {
362
+ ...prevParams,
363
+ locationValue: updatedLocations
364
+ };
365
+ });
366
+ };
367
+
368
+ const handleIncidentTypeFilterChange = (value) => {
369
+ setFormSearchParams((prevParams) => {
370
+ const currentIncTypeIds = prevParams.incidentTypeId;
371
+ const isChecked = currentIncTypeIds.includes(value);
372
+
373
+ // add/remove value
374
+ const updatedIncTypeIds = isChecked
375
+ ? currentIncTypeIds.filter((loc) => loc !== value)
376
+ : [...currentIncTypeIds, value];
377
+
378
+ return {
379
+ ...prevParams,
380
+ incidentTypeId: updatedIncTypeIds
381
+ };
382
+ });
383
+ };
384
+
385
+ const handleTrespassFilterChange = (value) => {
386
+ setFormSearchParams((prev) => {
387
+ const params = { ...prev };
388
+ if (value === 'none') {
389
+ delete params.currentTrespass;
390
+ delete params.expiredTrespass;
391
+ };
392
+ if (value === 'currentTrespass') {
393
+ params.currentTrespass = true;
394
+ delete params.expiredTrespass;
395
+ };
396
+ if (value === 'expiredTrespass') {
397
+ params.expiredTrespass = true;
398
+ delete params.currentTrespass;
399
+ };
400
+ return params;
401
+ });
402
+ };
403
+
404
+ const handleSuppressedFilterChange = (event) => {
405
+ const { value } = event.target;
406
+ // console.log("value --> ", value);
407
+ setFormSearchParams((prev) => ({
408
+ ...prev,
409
+ staffSuppress: value
410
+ }));
411
+ };
412
+
413
+ // START locations help
414
+ const allFilteredLocations = locationItems.filter(item => item.label.toLowerCase().includes(locationInputValue.toLowerCase()));
415
+ const filteredLocations = allFilteredLocations.slice(0, locationsVisibleCount);
416
+ const handleMoreLocationsClick = () => {
417
+ setLocationsVisibleCount((prevCount) => {
418
+ return Math.min(prevCount + 5, allFilteredLocations.length)
419
+ });
420
+ };
421
+
422
+ const loadMoreLocations = () => {
423
+ handleMoreLocationsClick();
424
+ if (!locationsHasExpanded) {
425
+ setLocationsHasExpanded(true);
426
+ };
427
+ };
428
+
429
+ const locationsContainerStyle = {
430
+ maxHeight: locationsHasExpanded ? '175px' : '125px',
431
+ overflowX: 'clip',
432
+ overflowY: 'auto',
433
+ marginTop: '8px'
434
+ };
435
+ // END locations help
436
+
437
+ // START incident types help
438
+ const allFilteredIncidentTypes = incidentTypesItems.filter(item => item.label.toLowerCase().includes(incidentTypeInputValue.toLowerCase()));
439
+ const filteredIncidentTypes = allFilteredIncidentTypes.slice(0, incidentTypesVisibleCount);
440
+ const handleMoreIncidentTypesClick = () => {
441
+ setIncidentTypesVisibleCount((prevCount) => {
442
+ return Math.min(prevCount + 5, allFilteredIncidentTypes.length)
443
+ });
444
+ };
445
+
446
+ const loadMoreIncTypes = () => {
447
+ handleMoreIncidentTypesClick();
448
+ if (!incTypesHasExpanded) {
449
+ setIncTypesHasExpanded(true);
450
+ };
451
+ };
452
+
453
+ const incTypesContainerStyle = {
454
+ maxHeight: incTypesHasExpanded ? '175px' : '125px',
455
+ overflowX: 'clip',
456
+ overflowY: 'auto',
457
+ marginTop: '8px'
458
+ };
459
+ // END incident types help
460
+
461
+ const searchableIndexes = [
462
+ {
463
+ label: intl.formatMessage({
464
+ id: "search-pane.searchableIndex-label-keyword"
465
+ }),
466
+ value: 'keyword'
467
+ },
468
+ {
469
+ label: intl.formatMessage({
470
+ id: "search-pane.searchableIndex-label-name-or-barcode"
471
+ }),
472
+ value: 'name-or-barcode'
473
+ },
474
+ {
475
+ label: intl.formatMessage({
476
+ id: "search-pane.searchableIndex-label-customer-desc"
477
+ }),
478
+ value: 'customer-desc'
479
+ },
480
+ {
481
+ label: intl.formatMessage({
482
+ id: "search-pane.searchableIndex-label-witnessed-by"
483
+ }),
484
+ value: 'witnessed-by'
485
+ },
486
+ {
487
+ label: intl.formatMessage({
488
+ id: "search-pane.searchableIndex-label-created-by"
489
+ }),
490
+ value: 'created-by'
491
+ }
492
+ ];
493
+
494
+ return (
495
+ <Paneset>
496
+ <Pane
497
+ paneTitle={<FormattedMessage id="search-pane.paneTitle" />}
498
+ defaultWidth="25%"
499
+ {...props}
500
+ >
501
+ {queryString && <GetListDynamicQuery query={queryString} />}
502
+
503
+ <GetIncidentTypesDetails
504
+ context='incidents'
505
+ />
506
+ <GetLocationsInService />
507
+ <GetLocations />
508
+ <GetOrgLocaleSettings />
509
+
510
+ <Row style={{ marginTop: '35px' }}>
511
+ <Col xs={10}>
512
+ <Headline
513
+ size="large"
514
+ margin="medium"
515
+ tag="h2"
516
+ id="searchField-label"
517
+ >
518
+ <FormattedMessage id="search-pane.searchField-h2-label" />
519
+ </Headline>
520
+ </Col>
521
+ </Row>
522
+
523
+ <Row>
524
+ <Col xs={12}>
525
+ <SearchField
526
+ aria-labelledby="searchField-label"
527
+ key={resetKey}
528
+ onChange={handleParamsValueChange}
529
+ name="term"
530
+ value={formSearchParams.term}
531
+ searchableIndexes={searchableIndexes}
532
+ onChangeIndex={handleIndexChange}
533
+ onClear={handleClearSearchField}
534
+ onKeyDown={handleKeyDown}
535
+ />
536
+
537
+ <Row style={{ marginTop: '45px' }}>
538
+ <Col xs={10}>
539
+ <Headline size="large" margin="medium" tag="h2">
540
+ <FormattedMessage id="search-pane.filters-h2-label" />
541
+ </Headline>
542
+ </Col>
543
+ </Row>
544
+
545
+ <Accordion label={
546
+ <FormattedMessage id="search-pane.accordion-label-location" />
547
+ }>
548
+ <Row>
549
+ <Col xs={10}>
550
+ <AutoSuggest
551
+ value={locationInputValue}
552
+ items={[]}
553
+ onChange={setLocationInputValue}
554
+ menuStyle={{ display: 'none' }}
555
+ renderValue={(val) => val || ''} //render item in input field
556
+ />
557
+ </Col>
558
+ </Row>
559
+ <Row>
560
+ <Col xs={12} style={{ marginLeft: '10px' }}>
561
+ <div style={locationsContainerStyle}>
562
+ {filteredLocations.map((item) => (
563
+ <Checkbox
564
+ key={item.value}
565
+ label={item.label}
566
+ value={item.value}
567
+ checked={formSearchParams.locationValue.includes(item.value)}
568
+ onChange={() => handleLocationFilterChange(item.value)}
569
+ />
570
+ ))}
571
+ </div>
572
+ <div style={{ marginTop: '2px' }}>
573
+ {locationsVisibleCount < allFilteredLocations.length && (
574
+ <Button
575
+ onClick={loadMoreLocations}
576
+ >
577
+ <FormattedMessage id="more-button" />
578
+ </Button>
579
+ )}
580
+ </div>
581
+ </Col>
582
+ </Row>
583
+ </Accordion>
584
+
585
+ <Accordion label={
586
+ <FormattedMessage id="search-pane.accordion-label-incident-types" />
587
+ }>
588
+ <Row>
589
+ <Col xs={10}>
590
+ <AutoSuggest
591
+ value={incidentTypeInputValue}
592
+ items={[]}
593
+ onChange={setIncidentTypeInputValue}
594
+ menuStyle={{ display: 'none' }}
595
+ renderValue={(val) => val || ''} //render item in input field
596
+ />
597
+ </Col>
598
+ </Row>
599
+ <Row>
600
+ <Col xs={12} style={{ marginLeft: '10px' }}>
601
+ <div style={incTypesContainerStyle}>
602
+ {filteredIncidentTypes.map((item) => (
603
+ <Checkbox
604
+ value={item.value}
605
+ key={item.value}
606
+ label={item.label}
607
+ checked={formSearchParams.incidentTypeId.includes(item.value)}
608
+ onChange={() => handleIncidentTypeFilterChange(item.value)}
609
+ />
610
+ ))}
611
+ </div>
612
+ <div style={{ marginTop: '2px' }}>
613
+ {incidentTypesVisibleCount < allFilteredIncidentTypes.length && (
614
+ <Button
615
+ onClick={loadMoreIncTypes}
616
+ >
617
+ <FormattedMessage id="more-button" />
618
+ </Button>
619
+ )}
620
+ </div>
621
+ </Col>
622
+ </Row>
623
+ </Accordion>
624
+ <hr/>
625
+
626
+ <Row style={{ marginTop: '25px'}}>
627
+ <Col xs={8}>
628
+ <TextField
629
+ disabled={disableWitnessedByTextField}
630
+ value={formSearchParams.witnessedBy || ''}
631
+ label={
632
+ <FormattedMessage id= "search-pane.witnessed-by-text-field-label"/>
633
+ }
634
+ onChange={handleWitnessedByChange}
635
+ />
636
+ </Col>
637
+ </Row>
638
+
639
+ <Row>
640
+ <Col xs={8}>
641
+ <TextField
642
+ disabled={disableCreatedByTextField}
643
+ value={formSearchParams.createdBy || ''}
644
+ label={
645
+ <FormattedMessage id="search-pane.created-by-text-field-label" />
646
+ }
647
+ onChange={handleCreatedByChange}
648
+ />
649
+ </Col>
650
+ </Row>
651
+ </Col>
652
+ </Row>
653
+
654
+ <Row>
655
+ <Col xs={10} style={{ marginTop: '12px' }}>
656
+ <Datepicker
657
+ label={
658
+ <FormattedMessage id="search-pane.date-picker-from-label" />
659
+ }
660
+ name="startDate"
661
+ value={formSearchParams.startDate || ''}
662
+ onChange={handleParamsValueChange}
663
+ />
664
+ <Datepicker
665
+ label={<FormattedMessage id="search-pane.date-picker-to-label" />}
666
+ name="endDate"
667
+ value={formSearchParams.endDate || ''}
668
+ onChange={handleParamsValueChange}
669
+ />
670
+ </Col>
671
+ </Row>
672
+
673
+ <Row>
674
+ <Col xs={12}>
675
+ <Headline size="large" tag="h2" style={{ marginTop: '15px'}}>
676
+ <FormattedMessage id="search-pane.trespass-status-label" />
677
+ </Headline>
678
+ </Col>
679
+ </Row>
680
+
681
+ <Row style={{ marginTop: '-15px'}}>
682
+ <Col xs={12}>
683
+ <RadioButton
684
+ label={<FormattedMessage
685
+ id="search-pane.radio-button-all-statuses-label"
686
+ />}
687
+ checked={!formSearchParams.currentTrespass && !formSearchParams.expiredTrespass}
688
+ onChange={() => handleTrespassFilterChange('none')}
689
+ />
690
+ </Col>
691
+ </Row>
692
+
693
+ <Row>
694
+ <Col xs={12}>
695
+ <RadioButton
696
+ label={<FormattedMessage
697
+ id="search-pane.radio-button-current-trespass-label"
698
+ />}
699
+ checked={formSearchParams.currentTrespass === true}
700
+ onChange={() => handleTrespassFilterChange('currentTrespass')}
701
+ />
702
+ </Col>
703
+ </Row>
704
+
705
+ <Row>
706
+ <Col xs={12}>
707
+ <RadioButton
708
+ label={<FormattedMessage
709
+ id="search-pane.radio-button-expired-trespass-label"
710
+ />}
711
+ checked={formSearchParams.expiredTrespass === true}
712
+ onChange={() => handleTrespassFilterChange('expiredTrespass')}
713
+ />
714
+ </Col>
715
+ </Row>
716
+
717
+ <Row style={{ marginTop: '20px' }}>
718
+ <Col xs={12}>
719
+ <Accordion
720
+ closedByDefault
721
+ label={<FormattedMessage
722
+ id="search-pane.accordion-label-staff-suppressed"
723
+ />}>
724
+ <Row>
725
+ <Col xs={12}>
726
+ <RadioButton
727
+ name='staffSuppress'
728
+ value='non'
729
+ checked={formSearchParams.staffSuppress === 'non'}
730
+ label={<FormattedMessage
731
+ id="search-pane.radio-button-suppressed-non-label"
732
+ />}
733
+ onChange={handleSuppressedFilterChange}
734
+ />
735
+ </Col>
736
+ </Row>
737
+ <Row>
738
+ <Col xs={12}>
739
+ <RadioButton
740
+ name='staffSuppress'
741
+ value='suppressed'
742
+ checked={formSearchParams.staffSuppress === 'suppressed'}
743
+ label={<FormattedMessage
744
+ id="search-pane.radio-button-suppressed-suppressed-label"
745
+ />}
746
+ onChange={handleSuppressedFilterChange}
747
+ />
748
+ </Col>
749
+ </Row>
750
+ <Row>
751
+ <Col xs={12}>
752
+ <RadioButton
753
+ name='staffSuppress'
754
+ value='all'
755
+ checked={formSearchParams.staffSuppress === 'all'}
756
+ label={<FormattedMessage
757
+ id="search-pane.radio-button-suppressed-all-label"
758
+ />}
759
+ onChange={handleSuppressedFilterChange}
760
+ />
761
+ </Col>
762
+ </Row>
763
+ </Accordion>
764
+ </Col>
765
+ </Row>
766
+
767
+ <Row>
768
+ <Col xs={12}>
769
+ <Button
770
+ fullWidth
771
+ style={{ marginTop: '23px' }}
772
+ buttonStyle="primary"
773
+ onClick={() => handleSubmit(true)}
774
+ >
775
+ <FormattedMessage id="search-button" />
776
+ </Button>
777
+ </Col>
778
+ </Row>
779
+
780
+ <Row>
781
+ <Col xs={12}>
782
+ <Button
783
+ fullWidth
784
+ style={{ backgroundColor: 'rgb(222, 221, 217)' }}
785
+ buttonStyle="disabled"
786
+ onClick={handleResetAll}
787
+ >
788
+ <Icon icon="times-circle-solid">
789
+ <FormattedMessage id="search-pane.reset-all-button" />
790
+ </Icon>
791
+ </Button>
792
+ </Col>
793
+ </Row>
794
+ </Pane>
795
+
796
+ <ResultsPane
797
+ {...props}
798
+ handlePagination={handlePagination}
799
+ />
800
+ {isDetailsPaneOpen && <DetailsPane
801
+ appliedSearchParams={appliedSearchParams}
802
+ {...props}
803
+ />}
804
+ {isEditPaneOpen && <EditPane {...props} />}
805
+ {isCreatePaneOpen && <CreatePane {...props} />}
806
+ </Paneset>
807
+ );
808
+ };
809
+
810
+ export default IncidentsPaneset;