@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,1267 @@
1
+ import React, { useState, useEffect, useMemo } from 'react';
2
+ import { useIntl, FormattedMessage } from 'react-intl';
3
+ import { useParams, useHistory, useLocation } from 'react-router-dom';
4
+ import { useStripes } from '@folio/stripes/core';
5
+ import {
6
+ Avatar,
7
+ Button,
8
+ Headline,
9
+ Icon,
10
+ KeyValue,
11
+ Pane,
12
+ PaneHeader,
13
+ AccordionSet,
14
+ Accordion,
15
+ ExpandAllButton,
16
+ Label,
17
+ MetaSection,
18
+ MessageBanner,
19
+ Row,
20
+ Col,
21
+ LoadingPane,
22
+ List,
23
+ } from '@folio/stripes/components';
24
+ import ProfilePicture from './helpers/ProfilePicture/ProfilePicture.js';
25
+ import GetDetails from './GetDetails';
26
+ import GetName from './GetName';
27
+ import GetNameCreatedBy from './GetNameCreatedBy';
28
+ import identifyCurrentTrespassDocs from './helpers/identifyCurrentTrespassDocs';
29
+ import GetLocationsInService from '../../settings/GetLocationsInService';
30
+ import GetIncidentTypesDetails from '../../settings/GetIncidentTypesDetails';
31
+ import ModalViewMedia from './ModalViewMedia';
32
+ import GetMedia from './GetMedia';
33
+ import Thumbnail from './Thumbnail';
34
+ import ThumbnailSkeleton from './ThumbnailSkeleton';
35
+ import ModalCustomWitness from './ModalCustomWitness';
36
+ // used for rendering instance values where only the date is considered:
37
+ import convertDateIgnoringTZ from './helpers/convertDateIgnoringTZ';
38
+ // convert for UI presentation:
39
+ import convertUTCISOToPrettyDate from './helpers/convertUTCISOToPrettyDate';
40
+ // convert for UI presentation:
41
+ import convertUTCISOToLocalePrettyTime from './helpers/convertUTCISOToLocalePrettyTime';
42
+ import sortTrespassDocuments from './helpers/sortTrespassDocuments.js';
43
+ import GetTrespassReasons from '../../settings/GetTrespassReasons';
44
+ import LinkedIncident from './LinkedIncident.js';
45
+ import { useIncidents } from '../../contexts/IncidentContext';
46
+
47
+ const DetailsPane = ({
48
+ ...props
49
+ }) => {
50
+ const stripes = useStripes();
51
+ const { id } = useParams();
52
+ const history = useHistory();
53
+ const location = useLocation();
54
+ const intl = useIntl();
55
+ const {
56
+ singleIncident, setSingleIncident,
57
+ closeDetailsPane,
58
+ openEditPane,
59
+ openLoadingDetails,
60
+ isLoadingDetails,
61
+ isModalViewImage,
62
+ openModalViewImage,
63
+ mode,
64
+ incidentTypesList,
65
+ locationsInService,
66
+ isImageArrayLoading,
67
+ openImageSkeleton, // set isImageArrayLoading to true
68
+ closeImageSkeleton, // set isImageArrayLoading to false
69
+ openModalCustomWitness,
70
+ documentError,
71
+ documentErrorMessage,
72
+ clearDocumentError,
73
+ trespassReasons
74
+ } = useIncidents();
75
+
76
+ useEffect(() => {
77
+ if (id) {
78
+ setSingleIncident({})
79
+ openLoadingDetails();
80
+ }
81
+ // eslint-disable-next-line react-hooks/exhaustive-deps
82
+ }, [id]);
83
+
84
+ const {
85
+ incidentLocation = '',
86
+ subLocation = '',
87
+ dateTimeOfIncident = '',
88
+ isApproximateTime = false,
89
+ incidentWitnesses = [],
90
+ detailedDescriptionOfIncident = '',
91
+ customers = [],
92
+ incidentTypes = [],
93
+ attachments = [],
94
+ createdBy = {},
95
+ metadata = {},
96
+ staffSuppressed = Boolean,
97
+ linkedToSummary = []
98
+ } = singleIncident;
99
+
100
+ // console.log("singleIncident --> ", JSON.stringify(singleIncident, null, 2))
101
+
102
+ const hasViewProfilePicturePerm = stripes.hasPerm('ui-users.profile-pictures.view');
103
+ const [incTypeTitles, setIncTypeTitles] = useState([]);
104
+ const [locName, setLocName] = useState('');
105
+ const [modalViewImageData, setModalViewImageData] = useState({});
106
+ const [mediaSrc, setMediaSrc] = useState({});
107
+ const [mediaArray, setMediaArray] = useState([]);
108
+ const [loadingStatus, setLoadingStatus] = useState({});
109
+ const [documents, setDocuments] = useState([]);
110
+ const [mostCurrentTrespassDocIds, setMostCurrentTrespassDocIds] = useState([]);
111
+ const [associatedKeyCustArray, setAssociatedKeyCustArray] = useState([]);
112
+ const [associatedKeyWitArray, setAssociatedKeyWitArray] = useState([]);
113
+ const [customersForRender, setCustomersForRender] = useState([]);
114
+ const [witnessesForRender, setWitnessesForRender] = useState([]);
115
+ const [createdById, setCreatedById] = useState('');
116
+ const [updatedById, setUpdatedById] = useState('');
117
+ const [readyCreatedByInUI, setReadyCreatedByinUI] = useState(null);
118
+ const [custWitViewObj, setCustWitViewObj] = useState({});
119
+ const [missingUsers, setMissingUsers] = useState([]);
120
+ const [createdByForRender, setCreatedByForRender] = useState({
121
+ id: '',
122
+ barcode: '',
123
+ firstName: '',
124
+ lastName: ''
125
+ }); // leveraged for both record 'createdBy' key and MetaSection 'createdBy'
126
+ const [updatedByForRender, setUpdatedByForRender] = useState({
127
+ id: '',
128
+ barcode: '',
129
+ firstName: '',
130
+ lastName: ''
131
+ }); // leveraged for MetaSection 'lastUpdatedBy'
132
+
133
+ useEffect(() => {
134
+ if (customers) {
135
+ const readyDataCustomers = customers.map((dataObj) => {
136
+ // handle names if not registered
137
+ if(!dataObj.registered) {
138
+ return {
139
+ ...dataObj,
140
+ associatedFirstName: dataObj.firstName,
141
+ associatedLastName: dataObj.lastName,
142
+ profilePicLinkOrUUID: '' // Not registered, couldn't have profile pic
143
+ }
144
+ // handle if registered and associatedKeyCustArray has values to work with
145
+ } else if (dataObj.registered
146
+ && associatedKeyCustArray
147
+ && associatedKeyCustArray.length > 0) {
148
+ const matchingCust = associatedKeyCustArray.find(
149
+ (cust) => cust.id === dataObj.id
150
+ );
151
+ if (matchingCust) {
152
+ // return a matched customer with associated names
153
+ return {
154
+ ...dataObj,
155
+ associatedFirstName: matchingCust.firstName,
156
+ associatedLastName: matchingCust.lastName,
157
+ profilePicLinkOrUUID: matchingCust.profilePicLinkOrUUID
158
+ };
159
+ }
160
+ };
161
+ // otherwise return obj
162
+ return dataObj;
163
+ });
164
+ setCustomersForRender(readyDataCustomers)
165
+ };
166
+ }, [customers, associatedKeyCustArray])
167
+
168
+ useEffect(() => {
169
+ if(associatedKeyWitArray && associatedKeyWitArray.length > 0) {
170
+ const readyDataWitnesses = incidentWitnesses.map((dataObj) => {
171
+ const matchingWit = associatedKeyWitArray.find(
172
+ (wit) => wit.id === dataObj.id
173
+ );
174
+ if(matchingWit) {
175
+ return {
176
+ ...dataObj,
177
+ associatedFirstName: matchingWit.firstName,
178
+ associatedLastName: matchingWit.lastName
179
+ };
180
+ }
181
+ return dataObj;
182
+ });
183
+ setWitnessesForRender(readyDataWitnesses)
184
+ }
185
+ }, [incidentWitnesses, associatedKeyWitArray]);
186
+
187
+ const associatedKeyPlaceSigned = (value) => {
188
+ // value is expected to tbe the id associated w/ locationInService obj
189
+ const locObject = locationsInService.find(loc => loc.id === value)
190
+ // return the obj's location (which is the pretty name)
191
+ return locObject ? locObject.location : '';
192
+ };
193
+
194
+ useEffect(() => {
195
+ if(createdBy && createdBy.id !== '') {
196
+ setCreatedById(createdBy.id)
197
+ }
198
+ }, [createdBy]);
199
+
200
+ useEffect(() => {
201
+ if(metadata && 'updatedByUserId' in metadata && metadata.updatedByUserId !== '') {
202
+ setUpdatedById(metadata.updatedByUserId)
203
+ }
204
+ }, [metadata]);
205
+
206
+ useEffect(() => {
207
+ if(attachments && attachments.length > 0) {
208
+ const docs = attachments.filter((att) => att.contentType.startsWith('application'));
209
+ setDocuments(docs);
210
+
211
+ const medias = attachments.filter((att) => att.contentType.startsWith('image') || att.contentType.startsWith('video'));
212
+ setMediaArray(medias);
213
+
214
+ const newLoadingStatus = medias.reduce((acc, att) => ({
215
+ ...acc,
216
+ [att.id]: true // start all media as loading true
217
+ }), {});
218
+ setLoadingStatus(newLoadingStatus);
219
+
220
+ openImageSkeleton();
221
+ } else {
222
+ setDocuments([]);
223
+ setMediaArray([]);
224
+ setMediaSrc({});
225
+ setLoadingStatus({});
226
+ closeImageSkeleton();
227
+ }
228
+ }, [attachments]);
229
+
230
+ const sortedDocuments = useMemo(() => sortTrespassDocuments(documents, mostCurrentTrespassDocIds), [documents, mostCurrentTrespassDocIds])
231
+
232
+
233
+ const handleMediaUrl = (mediaUrl, attachmentId) => {
234
+ setMediaSrc(prev => ({ ...prev, [attachmentId]: mediaUrl }));
235
+ setLoadingStatus(prev => ({ ...prev, [attachmentId]: false }));
236
+ };
237
+
238
+ useEffect(() => {
239
+ const allImagesLoaded = Object.values(loadingStatus).every(status => !status);
240
+ if (allImagesLoaded) {
241
+ closeImageSkeleton();
242
+
243
+ }
244
+ }, [loadingStatus, closeImageSkeleton]);
245
+
246
+
247
+ useEffect(() => {
248
+ if (documents && documents.length > 0) {
249
+ const startStr = 'trespass-';
250
+ const current = identifyCurrentTrespassDocs(documents, startStr);
251
+ setMostCurrentTrespassDocIds(current);
252
+ }
253
+ }, [documents]);
254
+
255
+ const thumbnailStyle = { width: '100px', height: 'auto', objectFit: 'cover'};
256
+
257
+ useEffect(() => {
258
+ if(singleIncident && singleIncident.incidentLocation) {
259
+ const locObject = locationsInService.find(loc => loc.id === singleIncident.incidentLocation);
260
+ const name = locObject && locObject.location ? locObject.location : "unknown"
261
+ setLocName(name)
262
+ }
263
+ }, [singleIncident, incidentLocation, locationsInService])
264
+
265
+ useEffect(() => {
266
+ const ready = singleIncident?.incidentTypes?.length &&
267
+ incidentTypesList?.length;
268
+ if (!ready) {
269
+ setIncTypeTitles([]);
270
+ return
271
+ };
272
+
273
+ const byId = new Map(
274
+ (incidentTypesList ?? []).map(t => [t.id, t])
275
+ );
276
+
277
+ const fullTypes = singleIncident.incidentTypes.map(({ id }) => {
278
+ const found = byId.get(id);
279
+ return found ?? {
280
+ id,
281
+ title: <FormattedMessage id="incident-type-not-found-fallback"
282
+ values={{ id }}/>,
283
+ description: ''
284
+ };
285
+ });
286
+
287
+ setIncTypeTitles(fullTypes);
288
+ }, [singleIncident, incidentTypesList]);
289
+
290
+ const incTypeFormatter = (item, index) => {
291
+ if (!item) {
292
+ return null;
293
+ };
294
+ return (
295
+ <li key={item.id ?? index}>
296
+ {item.title ? item.title : 'error'} {item.description ? item.description : ''}
297
+ </li>
298
+ )
299
+ };
300
+
301
+ const handleMissingUsers = (userId) => {
302
+ setMissingUsers((prev) => {
303
+ return [ ...prev, userId]
304
+ })
305
+ };
306
+
307
+ const handleGetCustName = (userObj) => {
308
+ setAssociatedKeyCustArray((prevState) => {
309
+ return [...prevState, userObj]
310
+ })
311
+ };
312
+
313
+ const handleGetWitnessName = (witObj) => {
314
+ setAssociatedKeyWitArray((prevState) => {
315
+ return [...prevState, witObj]
316
+ })
317
+ };
318
+
319
+ const handleShowCustomWitModalAsDetails = (witObj) => {
320
+ setCustWitViewObj(witObj)
321
+ openModalCustomWitness()
322
+ };
323
+
324
+ const handleGetCreatedByName = (createdByNameObj) => {
325
+ if (createdByNameObj) {
326
+ setCreatedByForRender({
327
+ id: createdByNameObj.id,
328
+ barcode: createdByNameObj.barcode,
329
+ firstName: createdByNameObj.firstName,
330
+ lastName: createdByNameObj.lastName
331
+ })
332
+ } else {
333
+ setCreatedByForRender({
334
+ id: '',
335
+ barcode: '',
336
+ firstName: '',
337
+ lastName: ''
338
+ })
339
+ }
340
+ };
341
+
342
+ const handleGetUpdatedByName = (updatedByNameObj) => {
343
+ if (updatedByNameObj) {
344
+ setUpdatedByForRender({
345
+ id: updatedByNameObj.id,
346
+ barcode: updatedByNameObj.barcode,
347
+ firstName: updatedByNameObj.firstName,
348
+ lastName: updatedByNameObj.lastName
349
+ })
350
+ } else {
351
+ setUpdatedByForRender({
352
+ id: '',
353
+ barcode: '',
354
+ firstName: '',
355
+ lastName: ''
356
+ })
357
+ }
358
+ };
359
+
360
+ const witnessNames = witnessesForRender.map((wit) => {
361
+ const name = `${wit.associatedLastName}, ${wit.associatedFirstName}`;
362
+ return name;
363
+ });
364
+
365
+ const makeCreatedByLink = (createdByObject) => {
366
+ if (createdByObject.id && createdByObject.id !== '') {
367
+ return (
368
+ <a
369
+ href={`/users/preview/${createdByObject.id}`}
370
+ target="_blank"
371
+ aria-label="Link to created by in users application"
372
+ style={{
373
+ textDecoration: 'none',
374
+ color: 'rgb(0,0,238)',
375
+ fontWeight: 'bold',
376
+ }}
377
+ rel="noreferrer"
378
+ >
379
+ {`${createdByObject.lastName}, ${createdByObject.firstName}`}
380
+ </a>
381
+ )
382
+ } else {
383
+ return <Icon icon='spinner-ellipsis' size='small'></Icon>
384
+ }
385
+ };
386
+
387
+ useEffect(() => {
388
+ if (createdByForRender) {
389
+ setReadyCreatedByinUI(makeCreatedByLink(createdByForRender));
390
+ }
391
+ }, [createdByForRender]) // on render
392
+
393
+ const witnessItemFormatter = (wit) => {
394
+ const isCustomWitness = wit.isCustom === true;
395
+ return (
396
+ <li key={wit.id}>
397
+ {isCustomWitness ? (
398
+ <>
399
+ {wit.lastName}, {wit.firstName}
400
+ <button
401
+ style={{ paddingLeft: '15px' }}
402
+ onClick={() => handleShowCustomWitModalAsDetails(wit)}
403
+ type="button"
404
+ >
405
+ <Icon icon="report" size="medium" />
406
+ <FormattedMessage id="view-details-custom-witness"/>
407
+ </button>
408
+ </>
409
+ ) : <a
410
+ href={`/users/preview/${wit.id}`}
411
+ target="_blank"
412
+ aria-label="Link to created by in users application"
413
+ style={{
414
+ textDecoration: 'none',
415
+ color: 'rgb(0,0,238)',
416
+ fontWeight: 'bold',
417
+ }}
418
+ rel="noreferrer"
419
+ >
420
+ {`${wit.associatedLastName}, ${wit.associatedFirstName}`}
421
+ </a>
422
+ }
423
+ </li>
424
+ )
425
+ };
426
+
427
+ const style = {
428
+ display: 'block',
429
+ width: '50%',
430
+ marginTop: '10px',
431
+ };
432
+
433
+ const handleDismissDetails = () => {
434
+ setCreatedByForRender({
435
+ id: '',
436
+ barcode: '',
437
+ firstName: '',
438
+ lastName: ''
439
+ });
440
+ setUpdatedByForRender({
441
+ id: '',
442
+ barcode: '',
443
+ firstName: '',
444
+ lastName: ''
445
+ });
446
+ setCreatedById('');
447
+ setUpdatedById('');
448
+ closeDetailsPane();
449
+ setSingleIncident({});
450
+ clearDocumentError();
451
+ setMissingUsers([]);
452
+ setMediaSrc({});
453
+ setMediaArray([]);
454
+ setLoadingStatus({});
455
+
456
+ setDocuments([]);
457
+ closeImageSkeleton();
458
+
459
+ const lastListRoute = sessionStorage.getItem('lastTrackListRoute');
460
+
461
+ if (mode === 'createMode') {
462
+ history.push(`/incidents?limit=20&offset=0`);
463
+ } else if (lastListRoute && lastListRoute.startsWith('/incidents')) {
464
+ sessionStorage.removeItem('lastTrackListRoute');
465
+ history.push(lastListRoute);
466
+ } else {
467
+ history.push(`/incidents?limit=20&offset=0`);
468
+ }
469
+ };
470
+
471
+ const handleOpenEdit = (incidentId) => {
472
+ closeDetailsPane();
473
+ setAssociatedKeyCustArray([]);
474
+ setCustomersForRender([]);
475
+ setSingleIncident({});
476
+ setCreatedById('')
477
+ openEditPane();
478
+ clearDocumentError();
479
+ setMissingUsers([]);
480
+ setMediaSrc(null);
481
+ setMediaArray(null);
482
+ setLoadingStatus(null);
483
+ history.replace(`/incidents/${incidentId}/edit${location.search}`);
484
+ };
485
+
486
+ const handleImageClick = (imageObj) => {
487
+ setModalViewImageData(imageObj)
488
+ openModalViewImage();
489
+ };
490
+
491
+ const createMarkup = (content) => {
492
+ return {__html: content}
493
+ };
494
+
495
+ const witnessesListLabel = intl.formatMessage(
496
+ { id: `witnesses-list-label` },
497
+ { count: witnessNames.length,
498
+ bold: (chunks) => (
499
+ <strong style={{ color: '#A12A2A' }}>{chunks}</strong>
500
+ )
501
+ }
502
+ );
503
+
504
+ const incidentTypesListLabel = intl.formatMessage(
505
+ { id: `incident-types-list-label` },
506
+ { count: incTypeTitles.length }
507
+ );
508
+
509
+ const getActionMenu = ({ onToggle }) => (
510
+ <>
511
+ <Button
512
+ buttonStyle="primary"
513
+ style={style}
514
+ onClick={() => handleOpenEdit(id)}
515
+ >
516
+ <FormattedMessage id="edit-button" />
517
+ </Button>
518
+ </>
519
+ );
520
+
521
+ const actionMenu = stripes.hasPerm('ui-security-incident.edit')
522
+ ? getActionMenu
523
+ : undefined;
524
+
525
+ const trById = useMemo(
526
+ () => new Map(
527
+ (trespassReasons ?? []).map(tr => [tr.id, tr])
528
+ ), [trespassReasons]);
529
+
530
+
531
+ const itemsFormatterExclusionList = (item) => {
532
+ const found = trById.get(item.id);
533
+ // item.reason is persisted fallback clear text
534
+ return <li key={item} style={{ marginLeft: '10px' }}>{found?.reason ?? item.reason}</li>;
535
+ };
536
+
537
+ return (
538
+ <>
539
+ {id &&
540
+ <GetDetails id={id}
541
+ />}
542
+
543
+ {customers.map((cust) => {
544
+ // if customer is not registered (not in /Users db) then skip that cust object and return null (handled in useEffect instead)
545
+ if (!cust.registered) {
546
+ return null;
547
+ };
548
+ return (
549
+ <GetName
550
+ key={cust.id}
551
+ uuid={cust.id}
552
+ handleGetCustName={handleGetCustName}
553
+ handleMissingUsers={handleMissingUsers}
554
+ context='customer'/>
555
+ );
556
+ })}
557
+
558
+ {incidentWitnesses.map((wit) => (
559
+ <GetName
560
+ isCustomWitness={wit.isCustom === true ? wit : null}
561
+ key={wit.id}
562
+ uuid={wit.id}
563
+ handleGetWitnessName={handleGetWitnessName}
564
+ handleMissingUsers={handleMissingUsers}
565
+ context="witness"
566
+ />
567
+ ))}
568
+
569
+ {updatedById && updatedById !== '' && (
570
+ <GetName
571
+ uuid={updatedById}
572
+ handleGetUpdatedByName={handleGetUpdatedByName}
573
+ handleMissingUsers={handleMissingUsers}
574
+ context="updatedBy"
575
+ />
576
+ )}
577
+
578
+ {createdById && createdById !== '' && (
579
+ <GetNameCreatedBy
580
+ uuid={createdById}
581
+ handleGetCreatedByName={handleGetCreatedByName}
582
+ handleMissingUsers={handleMissingUsers}
583
+ />
584
+ )}
585
+
586
+ {isLoadingDetails || singleIncident?.id !== id ? (
587
+ <LoadingPane
588
+ defaultWidth="fill"
589
+ paneTitle={
590
+ <FormattedMessage id="details-pane.loading-pane-paneTitle" />
591
+ }
592
+ />
593
+ ) : (
594
+ <Pane
595
+ key={id}
596
+ paneTitle={<FormattedMessage id="details-pane.paneTitle" />}
597
+ defaultWidth="fill"
598
+ {...props}
599
+ renderHeader={(renderProps) => (
600
+ <PaneHeader
601
+ {...renderProps}
602
+ dismissible
603
+ onClose={handleDismissDetails}
604
+ paneTitle={<FormattedMessage id="details-pane.pane-header-paneTitle" />}
605
+ actionMenu={actionMenu}
606
+ />
607
+ )}>
608
+ {custWitViewObj && Object.keys(custWitViewObj).length > 0 && (
609
+ <ModalCustomWitness
610
+ custWitViewObj={custWitViewObj}
611
+ />
612
+ )}
613
+ {isModalViewImage &&
614
+ modalViewImageData &&
615
+ <ModalViewMedia
616
+ modalViewImageData={modalViewImageData}
617
+ />
618
+ }
619
+ <GetIncidentTypesDetails context='incidents'/>
620
+ <GetLocationsInService />
621
+ <GetTrespassReasons />
622
+
623
+ {/* if 404 for any /Users request get associated key for name, render MessageBanner with message and those unfound uuid(s) */}
624
+ <Row>
625
+ <Col xs={12}>
626
+ <div>
627
+ <MessageBanner
628
+ dismissible
629
+ type="error"
630
+ show={missingUsers.length > 0}
631
+ >
632
+ {<FormattedMessage
633
+ id="message-banner.error-missing-users-404"
634
+ values={{ ids: missingUsers.join(', ') }}
635
+ />}
636
+ </MessageBanner>
637
+ </div>
638
+ </Col>
639
+ </Row>
640
+
641
+ {/* if documentError render MessageBanner for documentErrorMessage */}
642
+ <Row>
643
+ <Col xs={12}>
644
+ <div>
645
+ <MessageBanner
646
+ dismissible
647
+ onExit={() => clearDocumentError()}
648
+ type="error"
649
+ show={documentError}
650
+ >
651
+ {<FormattedMessage
652
+ id="details-pane.error-generate-trespass-doc"
653
+ values={{ documentErrorMessage }}
654
+ />}
655
+ </MessageBanner>
656
+ </div>
657
+ </Col>
658
+ </Row>
659
+
660
+ <AccordionSet>
661
+ <ExpandAllButton />
662
+ <MetaSection
663
+ headingLevel={4}
664
+ useAccordion
665
+ showUserLink
666
+ createdDate={metadata.createdDate || null}
667
+ lastUpdatedDate={metadata.updatedDate || null}
668
+ createdBy={{
669
+ id: metadata.createdByUserId,
670
+ personal: {
671
+ firstName: createdByForRender.firstName,
672
+ lastName: createdByForRender.lastName
673
+ }
674
+ }}
675
+ lastUpdatedBy={{
676
+ id: metadata.updatedByUserId,
677
+ personal: {
678
+ firstName: updatedByForRender.firstName,
679
+ lastName: updatedByForRender.lastName,
680
+ }
681
+ }}
682
+ />
683
+
684
+ {staffSuppressed && staffSuppressed === true ? (
685
+ <div style={{ display: 'inline-block' }}>
686
+ <Icon size='small' icon='exclamation-circle' status='warn'>
687
+ </Icon>
688
+ <span style={{ marginLeft: '0.5rem' }}>
689
+ <FormattedMessage id="details-pane.staff-suppressed" />
690
+ </span>
691
+ </div>
692
+ ) : (
693
+ null
694
+ )}
695
+
696
+ <Accordion label={<FormattedMessage id="accordion-label-media" />}>
697
+ <div>
698
+ {mediaArray?.map((attachment) => (
699
+ <GetMedia
700
+ context='thumbnail'
701
+ contentType={attachment.contentType}
702
+ key={attachment.id}
703
+ id={id}
704
+ imageId={attachment.id}
705
+ mediaHandler={(mediaUrl) => handleMediaUrl(mediaUrl, attachment.id)}
706
+ />
707
+ ))}
708
+ </div>
709
+
710
+ <Row style={{ margin: '25px' }}>
711
+ <Col xs={1} style={{ visibility: 'hidden' }}></Col>
712
+ {mediaArray?.slice(0, 5).map((attachment) => (
713
+ <Col xs={2} key={attachment.id}>
714
+ {loadingStatus[attachment.id] && isImageArrayLoading ? <ThumbnailSkeleton />
715
+ : <Thumbnail
716
+ key={attachment.id}
717
+ context='details'
718
+ src={mediaSrc[attachment.id]}
719
+ alt={attachment.description}
720
+ imageDescription={attachment.description}
721
+ contentType={attachment.contentType}
722
+ style={thumbnailStyle}
723
+ handler={() => handleImageClick({
724
+ "id": id,
725
+ "imageId": attachment.id,
726
+ "key": attachment.id,
727
+ "alt": attachment.description,
728
+ "contentType": attachment.contentType,
729
+ "imageDescription": attachment.description
730
+ })}
731
+ />
732
+ }
733
+ </Col>
734
+ ))}
735
+ </Row>
736
+ <Row style={{ margin: '25px' }}>
737
+ <Col xs={1} style={{ visibility: 'hidden' }}></Col>
738
+ {mediaArray?.slice(5, 10).map((attachment) => (
739
+ <Col xs={2} key={attachment.id}>
740
+ {loadingStatus[attachment.id] && isImageArrayLoading ? <ThumbnailSkeleton />
741
+ : <Thumbnail
742
+ key={attachment.id}
743
+ context='details'
744
+ src={mediaSrc[attachment.id]}
745
+ alt={attachment.description}
746
+ imageDescription={attachment.description}
747
+ handler={() => handleImageClick({
748
+ "id": id,
749
+ "imageId": attachment.id,
750
+ "key": attachment.id,
751
+ "alt": attachment.description,
752
+ "contentType": attachment.contentType,
753
+ "imageDescription": attachment.description
754
+ })}
755
+ contentType={attachment.contentType}
756
+ style={thumbnailStyle}
757
+ />
758
+ }
759
+ </Col>
760
+ ))}
761
+ </Row>
762
+ </Accordion>
763
+ <Accordion
764
+ label={singleIncident && singleIncident.customerNa === true ? <FormattedMessage id="accordion-label-no-associated-customers"/>
765
+ : <FormattedMessage id="accordion-label-customers" />}
766
+ >
767
+
768
+ <div style={{ padding: '15px' }}>
769
+ <Row>
770
+ <Col xs={10}>
771
+ {customersForRender.map((cust) => {
772
+ // init if trespass and trespass has end date
773
+ const endDateOfTrespassISO = cust.trespass ? cust.trespass.endDateOfTrespass : null;
774
+
775
+ // make pretty for UI if end date iso exists
776
+ const endDateOfTrespassPretty = endDateOfTrespassISO ? convertDateIgnoringTZ(endDateOfTrespassISO) : null;
777
+
778
+ // init as new Date obj if end date iso exists (for comparison)
779
+ const endDateOfTrespass = endDateOfTrespassISO ? new Date(endDateOfTrespassISO) : null;
780
+
781
+ // compare for is expired else false
782
+ const isTrespassExpired = endDateOfTrespass ? endDateOfTrespass.getTime() < Date.now() : false;
783
+
784
+ const isTrespassed = !!cust.trespass;
785
+ const isDeclarationOfService = cust.trespass &&
786
+ cust.trespass.declarationOfService &&
787
+ Object.keys(cust.trespass.declarationOfService).length > 0;
788
+
789
+ // NOTE: via useEffect associated first and last names are set for un-registered customers with their incident report instance value
790
+ // whereas registered customers associated first and last names are set by current value of their /Users instance value
791
+ const { associatedFirstName, associatedLastName } = cust;
792
+ const notAvailable = intl.formatMessage({ id: "unknown-name-placeholder" });
793
+ const displayedFirstName = associatedFirstName === '' ? notAvailable : associatedFirstName;
794
+ const displayedLastName = associatedLastName === '' ? notAvailable : associatedLastName;
795
+ let keyedName = `${displayedLastName}, ${displayedFirstName}`;
796
+
797
+
798
+ let trespassStatus = ''
799
+ if (isTrespassed) {
800
+ if (!isTrespassExpired) {
801
+ trespassStatus = (<FormattedMessage
802
+ id="details-pane.trespass-active-until"
803
+ values={{ date: endDateOfTrespassPretty}}/>)
804
+ } else {
805
+ trespassStatus = (<FormattedMessage
806
+ id="details-pane.trespass-expired"
807
+ values={{ date: endDateOfTrespassPretty}}/>)
808
+ }
809
+ };
810
+ const isKnown = cust.registered === true;
811
+
812
+ return (
813
+ <Accordion
814
+ key={cust.id}
815
+ label={!isKnown ? <>
816
+ {keyedName}
817
+ <span
818
+ style={{
819
+ marginLeft: '10px',
820
+ marginRight: '10px'
821
+ }}
822
+ >
823
+ {trespassStatus}
824
+ </span>
825
+ {isTrespassed && !isTrespassExpired && (
826
+ <span style={{
827
+ marginLeft: '10px',
828
+ marginRight: '10px',
829
+ color: 'red'}}>
830
+ <Icon icon='times-circle-solid' size='small'></Icon>
831
+ </span>
832
+ )}
833
+ </> : <>
834
+ <a
835
+ href={`/users/preview/${cust.id}`}
836
+ target="_blank"
837
+ aria-label="Link to customer in users application"
838
+ style={{
839
+ textDecoration: 'none',
840
+ color: 'rgb(0,0,238)',
841
+ fontWeight: 'bold',
842
+ }}
843
+ rel="noreferrer"
844
+ >
845
+ {keyedName}
846
+ </a>
847
+ <span
848
+ style={{
849
+ marginLeft: '10px',
850
+ marginRight: '10px'
851
+ }}
852
+ >
853
+ {trespassStatus}
854
+ </span>
855
+ {isTrespassed && !isTrespassExpired && (
856
+ <span style={{
857
+ marginLeft: '10px',
858
+ marginRight: '10px',
859
+ color: 'red'}}>
860
+ <Icon icon='times-circle-solid' size='small'></Icon>
861
+ </span>
862
+ )}
863
+ </>}>
864
+
865
+ <Row style={{ marginTop: '15px' }}>
866
+ <Col xs={4}>
867
+ <Headline size="medium" tag="h2">
868
+ {cust.description ? (<FormattedMessage id="details-pane.customersForRender-cust-description" />) : (<FormattedMessage id="details-pane.customersForRender-no-cust-description" />)}
869
+ </Headline>
870
+ {cust.description ? (
871
+ <div
872
+ dangerouslySetInnerHTML={createMarkup(cust.description)}>
873
+ </div>)
874
+ : null
875
+ }
876
+ </Col>
877
+
878
+ {hasViewProfilePicturePerm && (
879
+ <Col xs={6}>
880
+ <KeyValue
881
+ label={<FormattedMessage id="ui-users.information.profilePicture" />}
882
+ value={
883
+ <ProfilePicture
884
+ profilePictureLink={cust.profilePicLinkOrUUID}
885
+ />}
886
+ />
887
+ </Col>
888
+ )}
889
+ </Row>
890
+
891
+ <Row style={{ marginTop: '15px' }}>
892
+ <Col xs={4}>
893
+ <Headline size="medium" tag="h2">
894
+ {cust.details && Object.values(cust.details).some(value => value !== '' && value !== null) ? (<FormattedMessage id="details-pane.customersForRender-identity-details" />) : (<FormattedMessage id="details-pane.customersForRender-no-identity-details" />) }
895
+ </Headline>
896
+ </Col>
897
+ </Row>
898
+
899
+ {cust.details && Object.values(cust.details).some(value => value !== '' && value !== null) ? (
900
+ <>
901
+ <Row>
902
+ <Col xs={2}>
903
+ <Headline size="medium" tag="h2">
904
+ <FormattedMessage id="modal-view-customer-details.headline-sex" />
905
+ </Headline>
906
+ <p>
907
+ {cust.details && cust.details.sex ? cust.details.sex : '-'}
908
+ </p>
909
+ </Col>
910
+
911
+ <Col xs={2}>
912
+ <Headline size="medium" tag="h2">
913
+ <FormattedMessage id="modal-view-customer-details.headline-race" />
914
+ </Headline>
915
+ <p>
916
+ {cust.details && cust.details.race ? cust.details.race : '-'}
917
+ </p>
918
+ </Col>
919
+ <Col xs={2}>
920
+ <Headline size="medium" tag="h2">
921
+ <FormattedMessage id="modal-view-customer-details.headline-height" />
922
+ </Headline>
923
+ <p>
924
+ {cust.details && cust.details.height
925
+ ? cust.details.height
926
+ : '-'}
927
+ </p>
928
+ </Col>
929
+ </Row>
930
+
931
+ <Row style={{ marginTop: '10px' }}>
932
+ <Col xs={2}>
933
+ <Headline size="medium" tag="h2">
934
+ <FormattedMessage id="modal-view-customer-details.headline-weight" />
935
+ </Headline>
936
+ <p>
937
+ {cust.details && cust.details.weight
938
+ ? cust.details.weight
939
+ : '-'}
940
+ </p>
941
+ </Col>
942
+ <Col xs={2}>
943
+ <Headline size="medium" tag="h2">
944
+ <FormattedMessage id="modal-view-customer-details.headline-hair" />
945
+ </Headline>
946
+ <p>
947
+ {cust.details && cust.details.hair ? cust.details.hair : '-'}
948
+ </p>
949
+
950
+ </Col>
951
+ <Col xs={2}>
952
+ <Headline size="medium" tag="h2">
953
+ <FormattedMessage id="modal-view-customer-details.headline-eyes" />
954
+ </Headline>
955
+ <p>
956
+ {cust.details && cust.details.eyes ? cust.details.eyes : '-'}
957
+ </p>
958
+ </Col>
959
+ <Col xs={2}>
960
+ <Headline size="medium" tag="h2">
961
+ <FormattedMessage id="modal-view-customer-details.headline-date-of-birth" />
962
+ </Headline>
963
+ <p>
964
+ {cust.details && cust.details.dateOfBirth ? convertDateIgnoringTZ(cust.details.dateOfBirth) : '-'}
965
+ </p>
966
+ </Col>
967
+ </Row>
968
+ </>
969
+ )
970
+ : null
971
+ }
972
+
973
+ {/* TRESPASS DETAILS */}
974
+ <Row style={{ marginTop: '15px' }}>
975
+ <Col xs={4}>
976
+ <Headline size="medium" tag="h2">
977
+ {cust.trespass && Object.values(cust.trespass).some(value => value !== '' && value !== null) ? (<FormattedMessage id="details-pane.customersForRender-trespass-details" />) : (<FormattedMessage id="details-pane.customersForRender-no-trespass-details" />) }
978
+ </Headline>
979
+ </Col>
980
+ </Row>
981
+ {isTrespassed ? (
982
+ <>
983
+ <Row style={{ marginLeft: ' 15px' }}>
984
+ <Col xs={4}>
985
+ <Headline size="medium" tag="h3">
986
+ <FormattedMessage id="modal-view-trespass.headline-exclusion-based-on" />
987
+ </Headline>
988
+ <List
989
+ label={
990
+ <FormattedMessage id="modal-view-trespass.headline-exclusion-based-on" />
991
+ }
992
+ listStyle="bullets"
993
+ items={cust.trespass.exclusionOrTrespassBasedOn}
994
+ itemFormatter={itemsFormatterExclusionList}
995
+ isEmptyMessage="-"
996
+ />
997
+ </Col>
998
+ </Row>
999
+ </>)
1000
+ : null}
1001
+
1002
+ {/* DECLARATION OF SERVICE DETAILS */}
1003
+ <Row style={{ marginTop: '15px' }}>
1004
+ <Col xs={4}>
1005
+ <Headline size="medium" tag="h2">
1006
+ {isDeclarationOfService ? (<FormattedMessage id="details-pane.customersForRender-declaration-of-service" />) : !isTrespassed ? null : (<FormattedMessage id="details-pane.customersForRender-no-declaration-of-service" />) }
1007
+ </Headline>
1008
+ </Col>
1009
+ </Row>
1010
+
1011
+ {isDeclarationOfService ? <Row style={{ marginTop: '15px', marginBottom: '30px'}}>
1012
+ <Col xs={2}>
1013
+ <Headline size="medium" tag="h3">
1014
+ <FormattedMessage id="modal-view-trespass.headline-date-served" />
1015
+ </Headline>
1016
+ <p>
1017
+ {cust.trespass.declarationOfService
1018
+ ? convertDateIgnoringTZ(cust.trespass.declarationOfService.date) : '-'}
1019
+ </p>
1020
+ </Col>
1021
+ <Col xs={2}>
1022
+ <Headline size="medium" tag="h3">
1023
+ <FormattedMessage id="modal-view-trespass.headline-place-signed" />
1024
+ </Headline>
1025
+ <p>
1026
+ {cust.trespass.declarationOfService
1027
+ ? associatedKeyPlaceSigned(cust.trespass.declarationOfService.placeSigned)
1028
+ : '-'}
1029
+ </p>
1030
+ </Col>
1031
+ <Col xs={2}>
1032
+ <Headline size="medium" tag="h3">
1033
+ <FormattedMessage id="modal-view-trespass.headline-title" />
1034
+ </Headline>
1035
+ <p>
1036
+ {cust.trespass.declarationOfService
1037
+ ? cust.trespass.declarationOfService.title
1038
+ : '-'}
1039
+ </p>
1040
+ </Col>
1041
+ <Col xs={2}>
1042
+ <Headline size="medium" tag="h3">
1043
+ <FormattedMessage id="modal-view-trespass.headline-signed" />
1044
+ </Headline>
1045
+ <p>
1046
+ {cust.trespass.declarationOfService && cust.trespass.declarationOfService.signature ? (<FormattedMessage id="details-pane.customersForRender-declaration-of-service-signature-yes"/>) : '-'}
1047
+ </p>
1048
+ </Col>
1049
+ </Row> : null
1050
+ }
1051
+ </Accordion>);
1052
+ })}
1053
+ </Col>
1054
+ </Row>
1055
+ </div>
1056
+ </Accordion>
1057
+
1058
+
1059
+ <Accordion label={<FormattedMessage id="details-pane.accordion-label.incident-details"/>}>
1060
+ <Row>
1061
+ <Col xs={3}>
1062
+ <Label
1063
+ style={{ marginTop: '5px' }}
1064
+ size="medium"
1065
+ tag="h2"
1066
+ id="incident-location-label"
1067
+ >
1068
+ <FormattedMessage id="details-pane.incident-location" />
1069
+ </Label>
1070
+ <Col>
1071
+ <p aria-labelledby="incident-location-label">
1072
+ {locName}
1073
+ </p>
1074
+ </Col>
1075
+ </Col>
1076
+
1077
+ {subLocation !== '' ? (
1078
+ <Col xs={3}>
1079
+ <Label
1080
+ style={{ marginTop: '5px' }}
1081
+ size="medium"
1082
+ tag="h2"
1083
+ id="incident-sub-location-label"
1084
+ >
1085
+ <FormattedMessage id="details-pane.sub-location" />
1086
+ </Label>
1087
+ <Col>
1088
+ <p aria-labelledby="incident-sub-location-label">
1089
+ {subLocation}
1090
+ </p>
1091
+ </Col>
1092
+ </Col>
1093
+ ) : null}
1094
+ </Row>
1095
+
1096
+ <Row>
1097
+ <Col xs={3}>
1098
+ <Label
1099
+ style={{ marginTop: '5px' }}
1100
+ size="medium"
1101
+ tag="h2"
1102
+ id="date-of-incident-label"
1103
+ >
1104
+ <FormattedMessage id="details-pane.date-of-incident" />
1105
+ </Label>
1106
+ <Col>
1107
+ <p aria-labelledby="date-of-incident-label">
1108
+ {convertUTCISOToPrettyDate(dateTimeOfIncident) || (<FormattedMessage id="not-available"/>)}
1109
+ </p>
1110
+ </Col>
1111
+ </Col>
1112
+
1113
+ <Col xs={3}>
1114
+ <Label
1115
+ style={{ marginTop: '5px' }}
1116
+ size="medium"
1117
+ tag="h2"
1118
+ id="time-of-incident-label"
1119
+ >
1120
+ <FormattedMessage id="details-pane.time-of-incident" />
1121
+ </Label>
1122
+ <Col>
1123
+ <p aria-labelledby="time-of-incident-label">
1124
+ {isApproximateTime ?
1125
+ (<>
1126
+ <FormattedMessage id="isApproximateTime" defaultMessage="Approximately" /> {convertUTCISOToLocalePrettyTime(dateTimeOfIncident)}</>
1127
+ )
1128
+ :
1129
+ (convertUTCISOToLocalePrettyTime(dateTimeOfIncident))
1130
+ }
1131
+ </p>
1132
+ </Col>
1133
+ </Col>
1134
+ </Row>
1135
+
1136
+ <Row style={{ marginTop: '25px' }}>
1137
+ <Col xs={6}>
1138
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
1139
+ <FormattedMessage
1140
+ id="witnesses-list-label"
1141
+ values={{
1142
+ count: witnessNames.length,
1143
+ bold: (chunks) => <strong>{chunks}</strong>,
1144
+ }}
1145
+ />
1146
+ </Label>
1147
+ <List
1148
+ aria-labelledby="witnesses-list-label"
1149
+ label={witnessesListLabel}
1150
+ listStyle="bullets"
1151
+ items={witnessesForRender}
1152
+ itemFormatter={witnessItemFormatter}
1153
+ />
1154
+ </Col>
1155
+ </Row>
1156
+
1157
+ <Row style={{ marginTop: '25px' }}>
1158
+ <Col xs={2}>
1159
+ <KeyValue
1160
+ label={linkedToSummary.length > 0 ? <FormattedMessage id="linked-incidents-label"/> : <FormattedMessage id="linked-incidents-empty-label"/>}
1161
+ value={linkedToSummary.length > 0 ? (
1162
+ <div style={{ display: 'grid', rowGap: '10px' }}>
1163
+ {linkedToSummary.map((ltS) => (
1164
+ <LinkedIncident
1165
+ key={ltS.id}
1166
+ summaryObj={ltS}
1167
+ renderContext='details'
1168
+ />
1169
+ ))}
1170
+ </div>
1171
+ ) : null
1172
+ }
1173
+ />
1174
+ </Col>
1175
+ </Row>
1176
+
1177
+ <Row style={{ marginTop: '13px' }}>
1178
+ <Col xs={8}>
1179
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
1180
+ {incidentTypesListLabel}
1181
+ </Label>
1182
+ <List
1183
+ listStyle="bullets"
1184
+ label={incidentTypesListLabel}
1185
+ items={incTypeTitles}
1186
+ itemFormatter={incTypeFormatter}
1187
+ />
1188
+ </Col>
1189
+ </Row>
1190
+
1191
+ <Row>
1192
+ <Col xs={6}>
1193
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
1194
+ <FormattedMessage id="details-pane.incident-description" />
1195
+ </Label>
1196
+ <Col>
1197
+ <div
1198
+ dangerouslySetInnerHTML={createMarkup(detailedDescriptionOfIncident)}>
1199
+ </div>
1200
+ </Col>
1201
+ </Col>
1202
+ </Row>
1203
+
1204
+ <Row style={{ marginTop: '15px' }}>
1205
+ <Col xs={6}>
1206
+ <Label
1207
+ style={{ marginTop: '5px' }}
1208
+ size="medium"
1209
+ tag="h2"
1210
+ id="created-by-label"
1211
+ >
1212
+ <FormattedMessage id="details-pane.created-by" />
1213
+ </Label>
1214
+ {readyCreatedByInUI}
1215
+ </Col>
1216
+ </Row>
1217
+ </Accordion>
1218
+
1219
+ <Accordion
1220
+ label={<FormattedMessage id="accordion-label-documents" />}
1221
+ >
1222
+ {/* This div does not render visible UI. Maps documents for GetMedia connected component */}
1223
+ <div>
1224
+ {documents?.map((doc) => (
1225
+ <GetMedia
1226
+ context='document'
1227
+ key={doc.id}
1228
+ id={id}
1229
+ imageId={doc.id}
1230
+ mediaHandler={(mediaUrl) => handleMediaUrl(mediaUrl, doc.id)}
1231
+ />
1232
+ ))}
1233
+ </div>
1234
+
1235
+ {/* This sorts and maps document buttons. The most recent documents are placed at the visual top of list with a clock icon to identify the most current trespass document(s). */}
1236
+ <div>
1237
+ {sortedDocuments.map((doc) => {
1238
+ const isMostCurrent = mostCurrentTrespassDocIds.includes(doc.id);
1239
+
1240
+ return (
1241
+ <Row xs={2} style={{ marginLeft: '15px' }} key={doc.id}>
1242
+ <Button
1243
+ allowAnchorClick={true}
1244
+ href={mediaSrc[doc.id]}
1245
+ target='_blank'
1246
+ style={{ marginTop: '15px' }}
1247
+ >
1248
+ {doc.description}
1249
+ </Button>
1250
+ {isMostCurrent && (
1251
+ <span style={{ marginLeft: '8px', marginTop: '20px' }}>
1252
+ <Icon icon='clock' size='small' />
1253
+ </span>
1254
+ )}
1255
+ </Row>
1256
+ );
1257
+ })}
1258
+ </div>
1259
+ </Accordion>
1260
+ </AccordionSet>
1261
+ </Pane>
1262
+ )}
1263
+ </>
1264
+ );
1265
+ };
1266
+
1267
+ export default DetailsPane;