@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,1215 @@
1
+ import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
2
+ import { useIntl, FormattedMessage } from 'react-intl';
3
+ import { useHistory } from 'react-router-dom';
4
+ import DOMPurify from 'dompurify';
5
+ import { decode } from 'html-entities';
6
+ import {
7
+ Accordion,
8
+ AccordionSet,
9
+ Button,
10
+ Checkbox,
11
+ Col,
12
+ Datepicker,
13
+ Editor,
14
+ ExpandAllButton,
15
+ List,
16
+ Icon,
17
+ KeyValue,
18
+ Label,
19
+ LoadingPane,
20
+ Pane,
21
+ PaneHeader,
22
+ PaneFooter,
23
+ Row,
24
+ Select,
25
+ Timepicker
26
+ } from '@folio/stripes/components';
27
+ import GetLocationsInService from '../../settings/GetLocationsInService';
28
+ import GetIncidentTypesDetails from '../../settings/GetIncidentTypesDetails';
29
+ import ModalSelectIncidentTypes from './ModalSelectIncidentTypes';
30
+ import ModalSelectKnownCustomer from './ModalSelectKnownCustomer';
31
+ import ModalSelectWitness from './ModalSelectWitness';
32
+ import ModalDescribeCustomer from './ModalDescribeCustomer';
33
+ import ModalTrespass from './ModalTrespass';
34
+ import ModalCustomerDetails from './ModalCustomerDetails';
35
+ import ModalAddMedia from './ModalAddMedia';
36
+ import CreateMedia from './CreateMedia';
37
+ import GetLocations from './GetLocations';
38
+ import GetSelf from './GetSelf';
39
+ import CreateReport from './CreateReport';
40
+ import makeId from '../../settings/helpers/makeId';
41
+ import ThumbnailTempPreSave from './ThumbnailTempPreSave';
42
+ import ModalCustomWitness from './ModalCustomWitness';
43
+ import GetTrespassTemplates from '../../settings/GetTrespassTemplates';
44
+ import GetTrespassReasons from '../../settings/GetTrespassReasons';
45
+ import stripHTML from './helpers/stripHTML';
46
+ import getTodayDate from './helpers/getTodayDate';
47
+ import isValidDateFormat from './helpers/isValidDateFormat';
48
+ import isValidTimeInput from './helpers/isValidTimeInput';
49
+ import { isSameHtml } from './helpers/isSameHtml.js';
50
+ // format to local date at midnight and one second in UTC ISO:
51
+ import formatDateToUTCISO from './helpers/formatDateToUTCISO';
52
+ // format for dateTimeOfIncident:
53
+ import formatDateAndTimeToUTCISO from './helpers/formatDateAndTimeToUTCISO';
54
+ import getCurrentTime from './helpers/getCurrentTime';
55
+ import hasFormChangedAtCreate from './helpers/hasFormChangedAtCreate.js';
56
+ import ModalDirtyFormWarn from './ModalDirtyFormWarn.js';
57
+ import ModalLinkIncident from './ModalLinkIncident.js';
58
+ import GetSummary from './GetSummary.js';
59
+ import LinkedIncident from './LinkedIncident.js';
60
+
61
+ import { useIncidents } from '../../contexts/IncidentContext';
62
+ import {
63
+ generateTrespassDocuments,
64
+ generatePDFAttachments
65
+ } from './helpers/trespassDocUtils.js';
66
+
67
+ const CreatePane = () => {
68
+ const history = useHistory();
69
+ const intl = useIntl();
70
+ const {
71
+ openModalSelectTypes,
72
+ closeCreatePane,
73
+ openModalUnknownCust,
74
+ openModalSelectKnownCust,
75
+ openModalSelectWitness,
76
+ openModalMedia,
77
+ idForMediaCreate,
78
+ setIdForMediaCreate,
79
+ formDataArrayForMediaCreate,
80
+ setFormDataArrayForMediaCreate,
81
+ selectedCustomers,
82
+ setSelectedCustomers,
83
+ selectedWitnesses,
84
+ setSelectedWitnesses,
85
+ setAttachmentsData,
86
+ self,
87
+ openModalTrespass,
88
+ openModalCustomerDetails,
89
+ incidentTypesList,
90
+ openModalCustomWitness,
91
+ isCreatingReport,
92
+ setIsCreatingReport,
93
+ locationsInService,
94
+ trespassTemplates,
95
+ triggerDocumentError,
96
+ trespassReasons
97
+ } = useIncidents();
98
+
99
+ const [linkedToSummaries, setLinkedToSummaries] = useState([]);// built from 'selectedIds'
100
+ const [selectedIds, setSelectedIds] = useState(() => new Set());// linkedTo ids
101
+ const [showModalLinkIncident, setShowModalLinkIncident] = useState(false);
102
+ const [trespassCustomerID, setTrespassCustomerID] = useState(null);
103
+ const [detailsCustomerID, setDetailsCustomerID] = useState(null);
104
+ const [postData, setPostData] = useState({});
105
+ const [subLocationsDataOptions, setSubLocationsDataOptions] = useState([]);
106
+ const [custWitEditID, setCustWitEditID] = useState('');
107
+ const [isNoCustomer, setIsNoCustomer] = useState(false);
108
+ const [trespassTemplate, setTrespassTemplate] = useState('');
109
+ const [showDirtyFormModal, setShowDirtyFormModal] = useState(false);
110
+ const editorWasTouchedRef = useRef(false);
111
+ const initialFormData = useRef({
112
+ customerNa: false,
113
+ customers: [],
114
+ incidentLocation: '',
115
+ subLocation: '',
116
+ dateOfIncident: getTodayDate(),
117
+ timeOfIncident: getCurrentTime(),
118
+ isApproximateTime: false,
119
+ detailedDescriptionOfIncident: '',
120
+ incidentWitnesses: [],
121
+ incidentTypes: [],
122
+ attachments: [],
123
+ linkedTo: [],
124
+ id: 'abcd1234-abcd-4bcd-8def-0123456789ab',
125
+ }).current;
126
+ const [formData, setFormData] = useState(initialFormData);
127
+
128
+ const hasFormChanged = () => hasFormChangedAtCreate({
129
+ formData,
130
+ initialFormData,
131
+ selectedCustomers,
132
+ selectedWitnesses,
133
+ selectedIds,
134
+ editorWasTouched: editorWasTouchedRef.current,
135
+ });
136
+
137
+ const idsArray = useMemo(
138
+ () => Array.from(selectedIds).sort(),
139
+ [selectedIds]
140
+ );
141
+
142
+ useEffect(() => {
143
+ if (trespassTemplates) {
144
+ const defaultTemplate = trespassTemplates.find((template) => {
145
+ return template.isDefault === true;
146
+ });
147
+ if (defaultTemplate) {
148
+ const templateValue = defaultTemplate.templateValue
149
+ setTrespassTemplate(templateValue)
150
+ };
151
+ };
152
+ }, [trespassTemplates]);
153
+
154
+ // useEffect(() => {
155
+ // console.log("formData: ", JSON.stringify(formData, null, 2))
156
+ // }, [formData]);
157
+
158
+ const toggleRowChecked = useCallback((id) => {
159
+ setSelectedIds(prev => {
160
+ const nextSet = new Set(prev);
161
+ nextSet.has(id) ? nextSet.delete(id) : nextSet.add(id);
162
+ // console.log('current checked --> ', JSON.stringify([...nextSet]));
163
+ return nextSet;
164
+ })
165
+ }, []);
166
+
167
+ const thumbnailStyle = { width: '100px', height: 'auto', objectFit: 'cover'};
168
+
169
+ // is leveraged by UI and Handlebars 'formatLocation' helper
170
+ const locationDataOptions = useMemo(() => {
171
+ const defaultValueLabel = [{
172
+ value: '',
173
+ label: <FormattedMessage
174
+ id="create-pane.locationDataOptions-label-select-location"/>
175
+ }];
176
+ const formattedLocations = locationsInService
177
+ ? locationsInService.map((loc) => ({
178
+ value: loc.id,
179
+ label: loc.location,
180
+ subLocations: loc.subLocations ? loc.subLocations : []
181
+ }))
182
+ : [{
183
+ value: '',
184
+ label: <FormattedMessage
185
+ id="create-pane.locationDataOptions-label-no-loaded"/>
186
+ }];
187
+ // console.log("formattedLocations: ", JSON.stringify(formattedLocations, null, 2))
188
+ return [
189
+ ...defaultValueLabel,
190
+ ...formattedLocations,
191
+ ];
192
+ }, [locationsInService]);
193
+
194
+ const runSubLocationsSelect = (value) => {
195
+ let subLocs;
196
+ let options;
197
+ const noSubLocationOption = [
198
+ {
199
+ value: 'No sub-location',
200
+ label: <FormattedMessage
201
+ id="create-pane.subLocations-label-default-no-sub-location"/>
202
+ },
203
+ ];
204
+ const noValueLabel = [
205
+ {
206
+ value: 'No sub-location',
207
+ label: <FormattedMessage
208
+ id="create-pane.subLocations-label-no-sub-location-available"
209
+ />
210
+ },
211
+ ];
212
+ const currentValue = locationDataOptions.find((loc) => loc.value === value);
213
+
214
+ if (currentValue && currentValue.subLocations && currentValue.subLocations.length > 0) {
215
+ subLocs = currentValue.subLocations.map((sub) => {
216
+ return { value: sub.name, label: `${sub.name} - ${sub.description}` };
217
+ });
218
+ options = [...noSubLocationOption, ...subLocs];
219
+ } else {
220
+ options = [...noValueLabel];
221
+ }
222
+ setSubLocationsDataOptions(options);
223
+ };
224
+
225
+ const handleChange = (eventOrValue) => {
226
+ let name;
227
+ let value;
228
+ if (eventOrValue && eventOrValue.target) {
229
+ ({ name, value } = eventOrValue.target);
230
+ } else {
231
+ // handles if no 'target' property in custom component, such as
232
+ // ( has array of selected options, not event object)
233
+ name = 'incidentWitnesses';
234
+ value = eventOrValue;
235
+ }
236
+ if (name === 'incidentWitnesses') {
237
+ const selectedRoles = value.map((item) => item.value);
238
+ const updatedWitnesses = formData.incidentWitnesses.map((witness) => ({
239
+ ...witness,
240
+ selected: selectedRoles.includes(witness.role),
241
+ }));
242
+ setFormData((prev) => ({
243
+ ...prev,
244
+ incidentWitnesses: updatedWitnesses,
245
+ }));
246
+ }
247
+ if (name === 'incidentLocation') {
248
+ runSubLocationsSelect(value);
249
+ setFormData((prev) => ({
250
+ ...prev,
251
+ [name]: value,
252
+ }));
253
+ } if (name === 'isApproximateTime') {
254
+ setFormData((prev) => ({
255
+ ...prev,
256
+ [name]: eventOrValue.target.checked,
257
+ }));
258
+ } else {
259
+ setFormData((prev) => ({
260
+ ...prev,
261
+ [name]: value,
262
+ }));
263
+ }
264
+ };
265
+
266
+ /*
267
+ Keep current HTML in a ref so keystrokes update w/out setting state.
268
+ This prevents react-quill's cleanup <-> setState feedback loop that otherwise
269
+ causes maximum update depth exceeded error when links are present in editor.
270
+ */
271
+ // local unsanitized buffer - doesn't trigger React re-renders
272
+ const draftRef = useRef(formData.detailedDescriptionOfIncident);
273
+
274
+ // fire on every key-press but only mutautes the ref
275
+ const handleEditorChange = (content) => {
276
+ draftRef.current = content;
277
+ if (!editorWasTouchedRef.current && content && content.trim() !== '') {
278
+ editorWasTouchedRef.current = true;
279
+ }
280
+ };
281
+
282
+ // commits once, with sanitized HTML
283
+ const handleEditorBlur = () => {
284
+ const sanitizedContent = DOMPurify.sanitize(draftRef.current);
285
+ setFormData(prev =>
286
+ isSameHtml(prev.detailedDescriptionOfIncident, sanitizedContent)
287
+ ? prev
288
+ : { ...prev, detailedDescriptionOfIncident: sanitizedContent}
289
+ );
290
+ };
291
+
292
+ const handleCustomerNa = (event) => {
293
+ setFormData((prev) => ({
294
+ ...prev,
295
+ customerNa: event.target.checked,
296
+ }));
297
+ setIsNoCustomer(prev => !prev );
298
+ };
299
+
300
+ const handleDismiss = () => {
301
+ if (hasFormChanged()) {
302
+ setShowDirtyFormModal(true);
303
+ } else {
304
+ handleCloseOnCancelDismiss();
305
+ }
306
+ };
307
+
308
+ const handleKeepEditing = () => {
309
+ setShowDirtyFormModal(false);
310
+ };
311
+
312
+ const handleDismissOnDirty = () => {
313
+ handleCloseOnCancelDismiss();
314
+ };
315
+
316
+ const handleCloseOnCancelDismiss = () => {
317
+ setIdForMediaCreate(null);
318
+ setFormDataArrayForMediaCreate(null);
319
+ setAttachmentsData([]);
320
+ setSelectedCustomers([]);
321
+ setSelectedWitnesses([]);
322
+ closeCreatePane();
323
+ history.push('/incidents');
324
+ };
325
+
326
+ const handleCloseNewOnSuccess = (id) => {
327
+ closeCreatePane();
328
+ history.push(`/incidents/${id}`);
329
+ };
330
+
331
+ const handleSubmit = async () => {
332
+ try {
333
+ const formattedCustomers = selectedCustomers.map((cust) => {
334
+ const customer = {
335
+ id: cust.id,
336
+ barcode: cust.barcode,
337
+ firstName: cust.firstName,
338
+ lastName: cust.lastName,
339
+ description: cust.description ? cust.description : '',
340
+ registered: cust.registered,
341
+ };
342
+ if (cust.trespass) {
343
+ customer.trespass = cust.trespass;
344
+ }
345
+ if (cust.details) {
346
+ customer.details = cust.details;
347
+ }
348
+ return customer;
349
+ });
350
+
351
+ const formattedWitnesses = selectedWitnesses.map((cust) => {
352
+ return {
353
+ id: cust.id,
354
+ firstName: cust.firstName,
355
+ lastName: cust.lastName,
356
+ ...(cust.barcode && { barcode: cust.barcode }),
357
+ ...(cust.role && { role: cust.role }),
358
+ ...(cust.phone && { phone: cust.phone }),
359
+ ...(cust.email && { email: cust.email }),
360
+ ...(cust.isCustom && { isCustom: cust.isCustom })
361
+ };
362
+ });
363
+
364
+ const customersList =
365
+ formattedCustomers.length > 0 ? formattedCustomers : [];
366
+
367
+ const allCustomersSet = customersList.map((cust) => {
368
+ let updatedCustomer = { ...cust };
369
+ if (cust.trespass) {
370
+ const trespassDescriptionRaw = cust.trespass.description || '';
371
+ const sanitizedCustomDesc = DOMPurify.sanitize(trespassDescriptionRaw);
372
+ const rawText = decode(sanitizedCustomDesc)
373
+ .replace(/<\/?[^>]+>/g, '')
374
+ .trim();
375
+
376
+ const useCustomDescription = rawText !== '';
377
+ if (!useCustomDescription && cust.trespass.description) {
378
+ // remove unused key
379
+ delete cust.trespass.description
380
+ }
381
+ updatedCustomer = {
382
+ ...updatedCustomer,
383
+ trespass: {
384
+ ...cust.trespass,
385
+ ...(cust.trespass.dateOfBirth
386
+ ? { dateOfBirth: formatDateToUTCISO(cust.trespass.dateOfBirth) }
387
+ : {}),
388
+ dateOfOccurrence: formatDateToUTCISO(formData.dateOfIncident),
389
+ ...(cust.trespass.endDateOfTrespass
390
+ ? {
391
+ endDateOfTrespass: formatDateToUTCISO(cust.trespass.endDateOfTrespass, 1),
392
+ }
393
+ : {}),
394
+ ...(cust.trespass.declarationOfService
395
+ ? {
396
+ declarationOfService: {
397
+ ...cust.trespass.declarationOfService,
398
+ date: formatDateToUTCISO(
399
+ cust.trespass.declarationOfService.date
400
+ ),
401
+ },
402
+ }
403
+ : {}),
404
+ /*
405
+ Use custom 'trespass.description' if it has meaningful text,
406
+ otherwise default to top level key 'detailedDescriptionOfIncident' for populating 'descriptionOfOccurrence'.
407
+ */
408
+ descriptionOfOccurrence: useCustomDescription
409
+ ? sanitizedCustomDesc
410
+ : formData.detailedDescriptionOfIncident,
411
+ ...(useCustomDescription && { description: sanitizedCustomDesc }),
412
+ witnessedBy: formattedWitnesses,
413
+ },
414
+ };
415
+ }
416
+
417
+
418
+ if (cust.details) {
419
+ const { dateOfBirth, ...restDetails } = cust.details;
420
+
421
+ updatedCustomer = {
422
+ ...updatedCustomer,
423
+ details: {
424
+ ...restDetails,
425
+ ...(dateOfBirth ?
426
+ { dateOfBirth: formatDateToUTCISO(cust.details.dateOfBirth) }
427
+ : {}),
428
+ },
429
+ };
430
+ }
431
+ // console.log("updatedCustomer: ", JSON.stringify(updatedCustomer, null, 2))
432
+ return updatedCustomer;
433
+ });
434
+
435
+ const readyToBeSaved = formData.attachments.map((mediaObj) => {
436
+ const {id, file, description, contentType} = mediaObj
437
+ return {
438
+ contentType: contentType,
439
+ description: description,
440
+ id: id,
441
+ file: file,
442
+ }
443
+ });
444
+
445
+ const linkedToArray = Array.from(selectedIds);
446
+
447
+ const data = {
448
+ ...formData,
449
+ linkedTo: linkedToArray,
450
+ attachments: readyToBeSaved,
451
+ customers: allCustomersSet,
452
+ dateTimeOfIncident: formatDateAndTimeToUTCISO(formData.dateOfIncident, formData.timeOfIncident),
453
+ incidentWitnesses: formattedWitnesses,
454
+ createdBy: {
455
+ barcode: self.barcode ? self.barcode : '',
456
+ id: self.id,
457
+ lastName: self.lastName,
458
+ firstName: self.firstName,
459
+ },
460
+ };
461
+
462
+ const helperDeps = { locationDataOptions, trespassReasons, self, triggerDocumentError};
463
+
464
+ // generate trespass documents
465
+ let readyTrespassDocuments = [];
466
+ try {
467
+ readyTrespassDocuments = generateTrespassDocuments(
468
+ allCustomersSet,
469
+ data,
470
+ trespassTemplate,
471
+ helperDeps
472
+ );
473
+ } catch (error) {
474
+ console.error(`Error at readyTrespassDocuments: ${error}`);
475
+ // triggerDocumentError(`Error in generateTrespassDocuments: ${error}`)
476
+ const errorMsg = error.message;
477
+ triggerDocumentError(<FormattedMessage
478
+ id="generate-trespass.error-doc-readyTrespassDocuments"
479
+ values={{ error: errorMsg }}
480
+ />)
481
+ readyTrespassDocuments = [];
482
+ };
483
+
484
+ let trespassDocumentPDFs = [];
485
+ try {
486
+ trespassDocumentPDFs = await generatePDFAttachments(
487
+ readyTrespassDocuments,
488
+ triggerDocumentError
489
+ );
490
+ } catch (error) {
491
+ console.error(`Unexpected error in PDF generation: ${error}`);
492
+ // triggerDocumentError(`Unexpected error in PDF generation: ${error}`)
493
+ const errorMsg = error.message;
494
+ triggerDocumentError(<FormattedMessage
495
+ id="generate-trespass.error-doc-unexpected-error"
496
+ values={{ error: errorMsg }}
497
+ />)
498
+ trespassDocumentPDFs = [];
499
+ };
500
+
501
+ const finalMergedAttachments = [...readyToBeSaved, ...trespassDocumentPDFs];
502
+
503
+ delete data.dateOfIncident;
504
+ delete data.timeOfIncident;
505
+
506
+ const finalData = {
507
+ ...data,
508
+ // CreateReport will unpack attachments and pass to CreateMedia on POST success
509
+ attachments: finalMergedAttachments
510
+ };
511
+
512
+ if (formData.incidentTypes && formData.incidentTypes.length > 0) {
513
+ data.incidentTypes = formData.incidentTypes;
514
+ }
515
+ // console.log("@CREATE - finalData at save: ", JSON.stringify(finalData, null, 2))
516
+ setIsCreatingReport(true);
517
+ setPostData(finalData);
518
+ setSelectedCustomers([]);
519
+ setSelectedWitnesses([]);
520
+ } catch (error) {
521
+ console.error('error in submit. error: ', error)
522
+ };
523
+ };
524
+
525
+ const parseMMDDYYYY = (dateString) => {
526
+ const [month, day, year] = dateString.split('/').map((val) => parseInt(val, 10));
527
+ return new Date(year, month - 1, day);
528
+ };
529
+
530
+ const dateIsNotInFuture = (dateString) => {
531
+ const todayString = getTodayDate();
532
+ const todayDate = parseMMDDYYYY(todayString);
533
+
534
+ todayDate.setHours(0,0,0,0);
535
+
536
+ const formDataDate = parseMMDDYYYY(dateString);
537
+
538
+ if (formDataDate <= todayDate) {
539
+ return true
540
+ } else if (formDataDate > todayDate){
541
+ return false
542
+ };
543
+ };
544
+
545
+ const isFormDataValid = () => {
546
+ const isCustomersPresent =
547
+ selectedCustomers && selectedCustomers.length > 0 || isNoCustomer === true;
548
+
549
+ const isIncidentDetailsPresent = formData.incidentLocation &&
550
+ formData.incidentLocation !== '' &&
551
+ formData.dateOfIncident && isValidDateFormat(formData.dateOfIncident) && dateIsNotInFuture(formData.dateOfIncident) &&
552
+ stripHTML(formData.detailedDescriptionOfIncident);
553
+
554
+ const isIncidentTypeSelected =
555
+ formData.incidentTypes && formData.incidentTypes.length > 0;
556
+
557
+ const isWitnessSelected = selectedWitnesses && selectedWitnesses.length > 0;
558
+
559
+ const isTimeValid = formData.timeOfIncident &&
560
+ formData.timeOfIncident !== '' && isValidTimeInput(formData.timeOfIncident);
561
+
562
+ return (
563
+ isCustomersPresent &&
564
+ isIncidentDetailsPresent &&
565
+ isIncidentTypeSelected &&
566
+ isWitnessSelected &&
567
+ isTimeValid
568
+ );
569
+ };
570
+
571
+ const handleIncidentTypeToggle = (type) => {
572
+ setFormData((prevFormData) => {
573
+ const currentTypes = new Set(prevFormData.incidentTypes.map(t => t.id));
574
+ if(currentTypes.has(type.id)) {
575
+ return {
576
+ ...prevFormData,
577
+ incidentTypes: prevFormData.incidentTypes.filter(t => t.id !== type.id)
578
+ };
579
+ } else {
580
+ return {
581
+ ...prevFormData,
582
+ incidentTypes: [...prevFormData.incidentTypes, type]
583
+ }
584
+ }
585
+ })
586
+ };
587
+
588
+ // handle rendering inc type 'title' via associated key of 'id'
589
+ // instead of the instance's inc type 'title'
590
+ const preparedIncidentTypes = useMemo(() => {
591
+ return formData.incidentTypes.map(incidentType => {
592
+ const foundType = incidentTypesList.find(type => type.id === incidentType.id);
593
+ return {
594
+ id: incidentType.id,
595
+ title: foundType ? foundType.title : "unknown"
596
+ };
597
+ });
598
+ }, [formData.incidentTypes, incidentTypesList]);
599
+
600
+ const handleRemoveType = (typeId) => {
601
+ const updatedIncidentTypes = formData.incidentTypes.filter(
602
+ (type) => type.id !== typeId
603
+ );
604
+ setFormData((prevFormData) => {
605
+ return {
606
+ ...prevFormData,
607
+ incidentTypes: updatedIncidentTypes,
608
+ };
609
+ });
610
+ };
611
+
612
+ const handleRemoveSelectedCustomer = (id) => {
613
+ const updatedSelectedCustomers = selectedCustomers.filter(
614
+ (cust) => cust.id !== id
615
+ );
616
+ setSelectedCustomers(updatedSelectedCustomers);
617
+ };
618
+
619
+ const handleRemoveSelectedWitness = (id) => {
620
+ const updatedSelectedWitnesses = selectedWitnesses.filter(
621
+ (cust) => cust.id !== id
622
+ );
623
+ setSelectedWitnesses(updatedSelectedWitnesses);
624
+ };
625
+
626
+ const handleOpenModalLinkIncident = () => {
627
+ setShowModalLinkIncident(true)
628
+ };
629
+
630
+ const handleCloseModalLinkIncident = () => {
631
+ setShowModalLinkIncident(false)
632
+ };
633
+
634
+ const handleTrashLinkedIncident = (toDeleteId) => {
635
+ toggleRowChecked(toDeleteId)
636
+ };
637
+
638
+ const handleAddMediaAtCreate = (mediaObj) => {
639
+ const readyMediaObj = {
640
+ ...mediaObj,
641
+ id: makeId(mediaObj.description),
642
+ description: mediaObj.description.trim(),
643
+ };
644
+ setFormData((prevFormData) => ({
645
+ ...prevFormData,
646
+ attachments: [...prevFormData.attachments, readyMediaObj]
647
+ }))
648
+ };
649
+
650
+ const handleRemoveUnsavedMediaCreate = (unsavedId) => {
651
+ setFormData((prevFormData) => ({
652
+ ...prevFormData,
653
+ attachments: prevFormData.attachments.filter((obj) => obj.id !== unsavedId)
654
+ }))
655
+ };
656
+
657
+ const handleShowTrespassFormModal = (id) => {
658
+ setTrespassCustomerID(id);
659
+ openModalTrespass();
660
+ };
661
+
662
+ const handleShowCustomerDetailsFormModal = (id) => {
663
+ setDetailsCustomerID(id);
664
+ openModalCustomerDetails();
665
+ };
666
+
667
+ const handleAddSelfAsWitness = () => {
668
+ setSelectedWitnesses((prevState) => {
669
+ const isSelfAlreadyWitness = prevState.some(
670
+ wit => wit.id === self.id
671
+ );
672
+ if(isSelfAlreadyWitness) {
673
+ return prevState;
674
+ };
675
+ return [...prevState, self];
676
+ });
677
+ };
678
+
679
+ const itemFormatterIncidentType = (item) => {
680
+ return (
681
+ <li key={item.id}>
682
+ {item.title}
683
+ <button
684
+ style={{ paddingLeft: '8px' }}
685
+ onClick={() => handleRemoveType(item.id)}
686
+ aria-label={`Remove ${item.title}`}
687
+ type="button"
688
+ >
689
+ <Icon icon="trash" size="medium" />
690
+ </button>
691
+ </li>
692
+ );
693
+ };
694
+
695
+ const itemFormatterSelectedCustomers = (item) => {
696
+ const notAvailable = intl.formatMessage({ id: "unknown-name-placeholder" });
697
+ const firstName = item.firstName === '' ? notAvailable : item.firstName;
698
+ const lastName = item.lastName === '' ? notAvailable : item.lastName;
699
+ return (
700
+ <li key={item.id}>
701
+ {lastName}, {firstName}
702
+ <button
703
+ style={{ paddingLeft: '25px' }}
704
+ onClick={() => handleRemoveSelectedCustomer(item.id)}
705
+ type="button"
706
+ >
707
+ <Icon icon="trash" size="medium" /> <FormattedMessage id="remove-button"/>
708
+ </button>
709
+ {item.details || item.description ? (
710
+ <button
711
+ style={{ paddingLeft: '15px' }}
712
+ onClick={() => handleShowCustomerDetailsFormModal(item.id)}
713
+ type="button"
714
+ >
715
+ <Icon icon="report" size="medium" />
716
+ <FormattedMessage id="edit-details-button"/>
717
+ </button>
718
+ ) : (
719
+ <button
720
+ style={{ paddingLeft: '15px' }}
721
+ onClick={() => handleShowCustomerDetailsFormModal(item.id)}
722
+ type="button"
723
+ >
724
+ <Icon icon="plus-sign" size="medium" />
725
+ <FormattedMessage id="add-details-button"/>
726
+ </button>
727
+ )}
728
+ {item.trespass ? (
729
+ <button
730
+ style={{ paddingLeft: '15px' }}
731
+ onClick={() => handleShowTrespassFormModal(item.id)}
732
+ type="button"
733
+ >
734
+ <Icon icon="report" size="medium" />
735
+ <FormattedMessage id="edit-trespass-button"/>
736
+ </button>
737
+ ) : (
738
+ <button
739
+ style={{ paddingLeft: '15px' }}
740
+ onClick={() => handleShowTrespassFormModal(item.id)}
741
+ type="button"
742
+ >
743
+ <Icon icon="plus-sign" size="medium" />
744
+ <FormattedMessage id="add-trespass-button"/>
745
+ </button>
746
+ )}
747
+ </li>
748
+ );
749
+ };
750
+
751
+ const handleEditCustomWit = (witId) => {
752
+ setCustWitEditID(witId)
753
+ openModalCustomWitness()
754
+ };
755
+
756
+ const handleOpenCustomWitness = () => {
757
+ openModalCustomWitness()
758
+ };
759
+
760
+ const itemFormatterSelectedWitnesses = (item) => {
761
+ const isCustom = item.isCustom;
762
+ return (
763
+ <li key={item.id}>
764
+ {item.lastName}, {item.firstName}
765
+ <button
766
+ style={{ paddingLeft: '8px' }}
767
+ onClick={() => handleRemoveSelectedWitness(item.id)}
768
+ aria-label={`Remove ${item.lastName}, ${item.firstName}`}
769
+ type="button"
770
+ >
771
+ <Icon icon="trash" size="medium" />
772
+ </button>
773
+ {isCustom && (
774
+ <button
775
+ style={{ paddingLeft: '12px' }}
776
+ onClick={() => handleEditCustomWit(item.id)}
777
+ aria-label={`Edit ${item.lastName}, ${item.firstName}`}
778
+ type="button"
779
+ >
780
+ <FormattedMessage id="edit-button"/>
781
+ </button>)
782
+ }
783
+ </li>
784
+ );
785
+ };
786
+
787
+ const handleOpenModalMedia = () => {
788
+ openModalMedia();
789
+ };
790
+
791
+ const customersListLabel = intl.formatMessage(
792
+ { id: 'customers-list-label' },
793
+ { count: selectedCustomers.length }
794
+ );
795
+
796
+ const incidentTypesListLabel = intl.formatMessage(
797
+ { id: 'incident-types-list-label' },
798
+ { count: preparedIncidentTypes.length }
799
+ );
800
+
801
+ const witnessesListLabel = intl.formatMessage(
802
+ { id: 'witnesses-list-label' },
803
+ { count: selectedWitnesses.length,
804
+ bold: (chunks) => (
805
+ <strong style={{ color: '#A12A2A' }}>{chunks}</strong>
806
+ ),
807
+ },
808
+ );
809
+
810
+ const footer = (
811
+ <PaneFooter
812
+ renderStart={
813
+ <Button onClick={handleDismiss}>
814
+ <FormattedMessage id="cancel-button" />
815
+ </Button>
816
+ }
817
+ renderEnd={
818
+ <Button
819
+ buttonStyle="primary"
820
+ onClick={handleSubmit}
821
+ disabled={!isFormDataValid()}
822
+ >
823
+ <FormattedMessage id="save-and-close-button" />
824
+ </Button>
825
+ }
826
+ />
827
+ );
828
+
829
+ return (
830
+ <>
831
+ {idForMediaCreate && formDataArrayForMediaCreate && (
832
+ <CreateMedia
833
+ context="new"
834
+ id={idForMediaCreate}
835
+ formDataArray={formDataArrayForMediaCreate}
836
+ handleCloseNewOnSuccess={handleCloseNewOnSuccess}
837
+ />
838
+ )}
839
+ {postData && (
840
+ <CreateReport
841
+ data={postData}
842
+ handleCloseNewOnSuccess={handleCloseNewOnSuccess}
843
+ />
844
+ )}
845
+
846
+ {showDirtyFormModal && (
847
+ <ModalDirtyFormWarn
848
+ handleKeepEditing={handleKeepEditing}
849
+ handleDismissOnDirty={handleDismissOnDirty}
850
+ />
851
+ )}
852
+
853
+ {isCreatingReport ? (
854
+ <LoadingPane
855
+ defaultWidth="fill"
856
+ paneTitle={<FormattedMessage id="create-pane.loading-pane-submit-paneTitle" />}
857
+ />
858
+ ) : (
859
+ <Pane
860
+ dismissible
861
+ defaultWidth="100%"
862
+ paneTitle={<FormattedMessage id="create-pane.paneTitle" />}
863
+ renderHeader={(renderProps) => (
864
+ <PaneHeader
865
+ {...renderProps}
866
+ dismissible
867
+ onClose={handleDismiss}
868
+ />
869
+ )}
870
+ footer={footer}
871
+ >
872
+
873
+ <ModalAddMedia
874
+ context='create'
875
+ handleAddMediaAtCreate={handleAddMediaAtCreate}
876
+ />
877
+ <ModalDescribeCustomer />
878
+ <ModalSelectKnownCustomer />
879
+ <ModalSelectWitness />
880
+ <GetSelf />
881
+ <ModalCustomWitness
882
+ custWitEditID={custWitEditID}
883
+ />
884
+
885
+ <GetTrespassTemplates />
886
+ <GetTrespassReasons />
887
+ <GetLocations />
888
+ <GetLocationsInService />
889
+ <GetIncidentTypesDetails context='incidents'/>
890
+ <ModalSelectIncidentTypes
891
+ handleIncidentTypeToggle={handleIncidentTypeToggle}
892
+ formDataIncidentTypes={formData.incidentTypes}
893
+ />
894
+ {trespassCustomerID &&
895
+ <ModalTrespass
896
+ customerID={trespassCustomerID}
897
+ modalContext='create-mode'
898
+ />
899
+ }
900
+ {detailsCustomerID && (
901
+ <ModalCustomerDetails customerID={detailsCustomerID} />
902
+ )}
903
+
904
+ {showModalLinkIncident && (
905
+ <ModalLinkIncident
906
+ toggleRowChecked={toggleRowChecked}
907
+ ids={selectedIds}
908
+ setIds={setSelectedIds}
909
+ handleCloseModalLinkIncident={handleCloseModalLinkIncident}
910
+ />
911
+ )}
912
+
913
+ <GetSummary
914
+ ids={idsArray}
915
+ onResult={setLinkedToSummaries}
916
+ />
917
+
918
+ <AccordionSet>
919
+ <ExpandAllButton />
920
+ <Accordion
921
+ label={<FormattedMessage id="accordion-label-customer-information" />}
922
+ >
923
+ {selectedCustomers.length > 0 ? null
924
+ : <Row>
925
+ <Col xs={2} style={{ marginTop: '10px', marginLeft: '10px', marginBottom: '10px'}}>
926
+ <Checkbox
927
+ label={<FormattedMessage id="customer-not-available"/>}
928
+ name='customerNa'
929
+ onChange={handleCustomerNa}
930
+ />
931
+ </Col>
932
+ </Row>}
933
+
934
+ {!isNoCustomer && (
935
+ <>
936
+ <Row>
937
+ <Col xs={3}>
938
+ <Button
939
+ onClick={openModalSelectKnownCust}
940
+ style={{ marginTop: '10px' }}
941
+ >
942
+ <FormattedMessage id="select-add-known-customer-button" />
943
+ </Button>
944
+ </Col>
945
+ </Row>
946
+
947
+ <Row>
948
+ <Col xs={3} style={{ marginTop: '10px' }}>
949
+ <Button onClick={openModalUnknownCust}>
950
+ <FormattedMessage id="describe-add-unknown-customer-button" />
951
+ </Button>
952
+ </Col>
953
+ </Row>
954
+
955
+ <Row>
956
+ <Col xs={9}>
957
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
958
+ <b>{customersListLabel}</b>
959
+ </Label>
960
+ <List
961
+ listStyle="bullets"
962
+ label={customersListLabel}
963
+ items={selectedCustomers}
964
+ isEmptyMessage={
965
+ <FormattedMessage
966
+ id="customers-list-is-empty-message"
967
+ values={{
968
+ bold: (chunks) => (
969
+ <strong style={{ color: '#A12A2A' }}>{chunks}</strong>
970
+ ),
971
+ }}
972
+ />
973
+ }
974
+ itemFormatter={itemFormatterSelectedCustomers}
975
+ />
976
+ </Col>
977
+ </Row>
978
+ </>
979
+ )}
980
+ </Accordion>
981
+
982
+ <Accordion label={<FormattedMessage id="accordion-label-incident" />}>
983
+ <Row>
984
+ <Col xs={3}>
985
+ <Select
986
+ required
987
+ label={
988
+ <FormattedMessage id="create-pane.location-select-label" />
989
+ }
990
+ name="incidentLocation"
991
+ value={formData.incidentLocation}
992
+ dataOptions={locationDataOptions}
993
+ onChange={handleChange}
994
+ />
995
+ </Col>
996
+
997
+ <Col xs={3}>
998
+ <Select
999
+ label={
1000
+ <FormattedMessage id="create-pane.sub-location-select-label" />
1001
+ }
1002
+ name="subLocation"
1003
+ value={formData.subLocation}
1004
+ dataOptions={subLocationsDataOptions}
1005
+ onChange={handleChange}
1006
+ />
1007
+ </Col>
1008
+ </Row>
1009
+
1010
+ <Row style={{ marginTop: '25px' }}>
1011
+ <Col xs={3}>
1012
+ <Datepicker
1013
+ required
1014
+ name="dateOfIncident"
1015
+ value={formData.dateOfIncident}
1016
+ label={
1017
+ <FormattedMessage id="create-pane.date-of-incident-date-picker-label" />
1018
+ }
1019
+ onChange={handleChange}
1020
+ />
1021
+ </Col>
1022
+
1023
+ <Col xs={2}>
1024
+ <Timepicker
1025
+ required
1026
+ name="timeOfIncident"
1027
+ value={formData.timeOfIncident}
1028
+ label={
1029
+ <FormattedMessage id="create-pane.time-of-incident-date-picker-label" />
1030
+ }
1031
+ onChange={handleChange}
1032
+ />
1033
+ </Col>
1034
+
1035
+ <Col xs={2} style={{ marginTop: '25px' }}>
1036
+ <Checkbox
1037
+ label='Approximate time'
1038
+ name='isApproximateTime'
1039
+ onChange={handleChange}
1040
+ />
1041
+ </Col>
1042
+ </Row>
1043
+
1044
+ <Row>
1045
+ <Col xs={4}>
1046
+ <Button
1047
+ onClick={openModalSelectTypes}
1048
+ style={{ marginTop: '15px' }}
1049
+ >
1050
+ <FormattedMessage id="create-pane.select-add-incident-type-button" />
1051
+ </Button>
1052
+ </Col>
1053
+ </Row>
1054
+
1055
+ <Row>
1056
+ <Col xs={4} style={{ paddingLeft: '20px' }}>
1057
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
1058
+ <b>{incidentTypesListLabel}</b>
1059
+ </Label>
1060
+ <List
1061
+ listStyle="bullets"
1062
+ label={incidentTypesListLabel}
1063
+ items={preparedIncidentTypes}
1064
+ isEmptyMessage={
1065
+ <FormattedMessage
1066
+ id="incident-types-list-is-empty-message"
1067
+ values={{
1068
+ bold: (chunks) => (
1069
+ <strong style={{ color: '#A12A2A' }}>{chunks}</strong>
1070
+ ),
1071
+ }}
1072
+ />
1073
+ }
1074
+ itemFormatter={itemFormatterIncidentType}
1075
+ />
1076
+ </Col>
1077
+ </Row>
1078
+
1079
+ <Row style={{ marginTop: '25px' }}>
1080
+ <Col xs={6}>
1081
+ <Editor
1082
+ required
1083
+ label={<FormattedMessage id="create-pane-detailedDescriptionIncident-editor-label"/>}
1084
+ value={draftRef.current}
1085
+ onChange={handleEditorChange}
1086
+ onBlur={handleEditorBlur}
1087
+ />
1088
+ </Col>
1089
+ </Row>
1090
+
1091
+ <Row>
1092
+ <Col xs={2} style={{ paddingTop: '25px' }}>
1093
+ <Button onClick={openModalSelectWitness}>
1094
+ <FormattedMessage id="select-add-witness-button" />
1095
+ </Button>
1096
+ </Col>
1097
+
1098
+ </Row>
1099
+
1100
+ <Row>
1101
+ <Col xs={2} style={{ paddingTop: '25px' }}>
1102
+ <Button onClick={handleAddSelfAsWitness}>
1103
+ <FormattedMessage id="add-self-witness-button" />
1104
+ </Button>
1105
+ </Col>
1106
+ </Row>
1107
+
1108
+ <Row>
1109
+ <Col xs={2} style={{ paddingTop: '25px' }}>
1110
+ <Button
1111
+ onClick={handleOpenCustomWitness}
1112
+ >
1113
+ <FormattedMessage id="add-custom-witness-button" />
1114
+ </Button>
1115
+ </Col>
1116
+ </Row>
1117
+
1118
+ <Row>
1119
+ <Col xs={2} style={{ paddingTop: '25px' }}>
1120
+ <Button onClick={handleOpenModalLinkIncident}>
1121
+ <FormattedMessage id="link-to-button" />
1122
+ </Button>
1123
+ </Col>
1124
+ </Row>
1125
+
1126
+ <Row>
1127
+ <Col xs={6} style={{ paddingTop: '20px' }}>
1128
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
1129
+ <FormattedMessage
1130
+ id="witnesses-list-label"
1131
+ values={{
1132
+ count: selectedWitnesses.length,
1133
+ bold: (chunks) => <strong>{chunks}</strong>,
1134
+ }}
1135
+ />
1136
+ </Label>
1137
+
1138
+ <List
1139
+ listStyle="bullets"
1140
+ label={witnessesListLabel}
1141
+ items={selectedWitnesses}
1142
+ isEmptyMessage={
1143
+ <FormattedMessage
1144
+ id="witnesses-list-is-empty-message"
1145
+ values={{
1146
+ bold: (chunks) => (
1147
+ <strong style={{ color: '#A12A2A' }}>{chunks}</strong>
1148
+ ),
1149
+ }}
1150
+ />
1151
+ }
1152
+ itemFormatter={itemFormatterSelectedWitnesses}
1153
+ />
1154
+ </Col>
1155
+ </Row>
1156
+
1157
+ {linkedToSummaries.length > 0 ? (
1158
+ <Row style={{ marginTop: '25px' }}>
1159
+ <Col xs={2}>
1160
+ <KeyValue
1161
+ label={<FormattedMessage id="linked-incidents-label"/>}
1162
+ value={
1163
+ <div style={{ display: 'grid', rowGap: '10px' }}>
1164
+ {linkedToSummaries.map((ltS) => (
1165
+ <LinkedIncident
1166
+ key={ltS.id}
1167
+ summaryObj={ltS}
1168
+ onDelete={handleTrashLinkedIncident}
1169
+ renderContext='create-edit'
1170
+ />
1171
+ ))}
1172
+ </div>
1173
+ }
1174
+ />
1175
+ </Col>
1176
+ </Row>
1177
+ ) : null}
1178
+ </Accordion>
1179
+
1180
+ <Accordion label={<FormattedMessage id="accordion-label-media" />}>
1181
+ <Row style={{ margin: '25px' }}>
1182
+ <Col xs={1} style={{ visibility: 'hidden' }}></Col>
1183
+ {formData.attachments.map((attachment) => (
1184
+ <Col xs={2} key={attachment.id}>
1185
+ <ThumbnailTempPreSave
1186
+ context='create'
1187
+ contentType={attachment.contentType}
1188
+ handleRemoveUnsavedMediaCreate={handleRemoveUnsavedMediaCreate}
1189
+ key={attachment.id}
1190
+ mediaId={attachment.id}
1191
+ src={attachment.filePreviewUrl}
1192
+ alt={attachment.description}
1193
+ imageDescription={attachment.description}
1194
+ style={thumbnailStyle}
1195
+ />
1196
+ </Col>
1197
+ ))}
1198
+ </Row>
1199
+
1200
+ <Row style={{ marginTop: '25px' }}>
1201
+ <Col xs={2}>
1202
+ <Button onClick={handleOpenModalMedia}>
1203
+ <FormattedMessage id="add-media-button" />
1204
+ </Button>
1205
+ </Col>
1206
+ </Row>
1207
+ </Accordion>
1208
+ </AccordionSet>
1209
+ </Pane>
1210
+ )}
1211
+ </>
1212
+ );
1213
+ };
1214
+
1215
+ export default CreatePane;