@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.
- package/.eslintrc +32 -0
- package/.github/workflows/CODEOWNERS +8 -0
- package/.github/workflows/pr-validation.yml +44 -0
- package/.github/workflows/release.yml +64 -0
- package/.prettierrc +6 -0
- package/.stripesclirc +4 -0
- package/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +4 -0
- package/LICENSE +201 -0
- package/README.md +16 -0
- package/administrator-documentation/roles-and-permissions.md +65 -0
- package/administrator-documentation/track-settings-admin-guide-sketch.md +192 -0
- package/administrator-documentation/using-the-application.md +192 -0
- package/icons/app.png +0 -0
- package/icons/app.svg +1 -0
- package/icons/playButton.png +0 -0
- package/icons/profilePicThumbnail.png +0 -0
- package/jest.config.js +10 -0
- package/module-descriptor.json +75 -0
- package/output/service-worker.js +0 -0
- package/package.json +146 -0
- package/src/components/incidents/ColumnChooser.js +37 -0
- package/src/components/incidents/CreateMedia.js +132 -0
- package/src/components/incidents/CreatePane.js +1215 -0
- package/src/components/incidents/CreatePane.test.js +138 -0
- package/src/components/incidents/CreateReport.js +102 -0
- package/src/components/incidents/DetailsPane.js +1267 -0
- package/src/components/incidents/DetailsPane.test.js +150 -0
- package/src/components/incidents/EditPane.js +2334 -0
- package/src/components/incidents/EditPane.test.js +187 -0
- package/src/components/incidents/GetDetails.js +55 -0
- package/src/components/incidents/GetListDQLinkIncident.js +81 -0
- package/src/components/incidents/GetListDynamicQuery.js +66 -0
- package/src/components/incidents/GetLocations.js +57 -0
- package/src/components/incidents/GetMedia.js +98 -0
- package/src/components/incidents/GetName.js +111 -0
- package/src/components/incidents/GetNameCreatedBy.js +94 -0
- package/src/components/incidents/GetOrgLocaleSettings.js +61 -0
- package/src/components/incidents/GetPatronGroups.js +52 -0
- package/src/components/incidents/GetSelf.js +65 -0
- package/src/components/incidents/GetSummary.js +110 -0
- package/src/components/incidents/IncidentTypeCard.js +53 -0
- package/src/components/incidents/IncidentTypeCard.test.js +133 -0
- package/src/components/incidents/IncidentsPaneset.js +810 -0
- package/src/components/incidents/IncidentsPaneset.test.js +128 -0
- package/src/components/incidents/LinkedIncident.js +86 -0
- package/src/components/incidents/ModalAddMedia.js +262 -0
- package/src/components/incidents/ModalAddMedia.test.js +97 -0
- package/src/components/incidents/ModalAttentionDecOfService.js +111 -0
- package/src/components/incidents/ModalCustomWitness.js +469 -0
- package/src/components/incidents/ModalCustomWitness.test.js +147 -0
- package/src/components/incidents/ModalCustomerDetails.js +480 -0
- package/src/components/incidents/ModalCustomerDetails.test.js +116 -0
- package/src/components/incidents/ModalDescribeCustomer.js +361 -0
- package/src/components/incidents/ModalDescribeCustomer.test.js +156 -0
- package/src/components/incidents/ModalDirtyFormWarn.js +62 -0
- package/src/components/incidents/ModalLinkIncident.js +1213 -0
- package/src/components/incidents/ModalLinkIncidentStyle.css +32 -0
- package/src/components/incidents/ModalSelectIncidentTypes.js +178 -0
- package/src/components/incidents/ModalSelectIncidentTypes.test.js +273 -0
- package/src/components/incidents/ModalSelectKnownCustomer.js +395 -0
- package/src/components/incidents/ModalSelectWitness.js +406 -0
- package/src/components/incidents/ModalSelectWitness.test.js +308 -0
- package/src/components/incidents/ModalStyle.css +44 -0
- package/src/components/incidents/ModalTrespass.js +741 -0
- package/src/components/incidents/ModalViewCustomerDetails.js +241 -0
- package/src/components/incidents/ModalViewMedia.js +86 -0
- package/src/components/incidents/ModalViewTrespass.js +210 -0
- package/src/components/incidents/ResultsPane.js +437 -0
- package/src/components/incidents/ResultsPane.test.js +120 -0
- package/src/components/incidents/SearchCustomerOrWitness.js +108 -0
- package/src/components/incidents/Thumbnail.js +72 -0
- package/src/components/incidents/ThumbnailMarkRemoval.js +38 -0
- package/src/components/incidents/ThumbnailSkeleton.js +30 -0
- package/src/components/incidents/ThumbnailStyles.js +49 -0
- package/src/components/incidents/ThumbnailTempPreSave.js +71 -0
- package/src/components/incidents/UpdateReport.js +84 -0
- package/src/components/incidents/__snapshots__/CreatePane.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/DetailsPane.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/EditPane.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/IncidentTypeCard.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/IncidentsPaneset.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/ModalAddMedia.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/ModalCustomerDetails.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/ModalSelectWitness.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/ResultsPane.test.js.snap +3 -0
- package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.css +5 -0
- package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.js +51 -0
- package/src/components/incidents/helpers/ProfilePicture/isAValidURL.js +3 -0
- package/src/components/incidents/helpers/ProfilePicture/useProfilePicture.js +127 -0
- package/src/components/incidents/helpers/buildQueryString.js +28 -0
- package/src/components/incidents/helpers/cleanFormValues.js +53 -0
- package/src/components/incidents/helpers/computeEditedCustomers.js +124 -0
- package/src/components/incidents/helpers/convertDateIgnoringTZ.js +8 -0
- package/src/components/incidents/helpers/convertUTCISOToLocalePrettyTime.js +15 -0
- package/src/components/incidents/helpers/convertUTCISOToPrettyDate.js +19 -0
- package/src/components/incidents/helpers/decodeParamsToForm.js +20 -0
- package/src/components/incidents/helpers/deepNormalizeForComparison.js +39 -0
- package/src/components/incidents/helpers/extractFilterString.js +12 -0
- package/src/components/incidents/helpers/formatDateAndTimeToUTCISO.js +14 -0
- package/src/components/incidents/helpers/formatDateToUTCISO.js +14 -0
- package/src/components/incidents/helpers/formatTimeToUTCISO.js +28 -0
- package/src/components/incidents/helpers/getCurrentTime.js +20 -0
- package/src/components/incidents/helpers/getTodayDate.js +12 -0
- package/src/components/incidents/helpers/handlebarsHelpers.js +148 -0
- package/src/components/incidents/helpers/hasFormChangedAtCreate.js +50 -0
- package/src/components/incidents/helpers/hasTopLevelChangeAffectedDeclaration.js +90 -0
- package/src/components/incidents/helpers/hasTopLevelFormChanged.js +111 -0
- package/src/components/incidents/helpers/identifyCurrentTrespassDocs.js +109 -0
- package/src/components/incidents/helpers/isSameHtml.js +13 -0
- package/src/components/incidents/helpers/isValidDateFormat.js +14 -0
- package/src/components/incidents/helpers/isValidTimeInput.js +11 -0
- package/src/components/incidents/helpers/isValidUTCTimeFormat.js +14 -0
- package/src/components/incidents/helpers/parseMMDDYYYY.js +7 -0
- package/src/components/incidents/helpers/parseQueryString.js +16 -0
- package/src/components/incidents/helpers/sortTrespassDocuments.js +44 -0
- package/src/components/incidents/helpers/stripHTML.js +11 -0
- package/src/components/incidents/helpers/trespassDocUtils.js +197 -0
- package/src/components/incidents/helpers/validateTrespassDetails.js +37 -0
- package/src/components/incidents/usePersistedColModalLink.js +70 -0
- package/src/components/incidents/usePersistedColumns.js +70 -0
- package/src/components/incidents/usePersistedSort.js +23 -0
- package/src/components/incidents/usePersistedSortModalLink.js +23 -0
- package/src/contexts/IncidentContext.js +433 -0
- package/src/index.js +61 -0
- package/src/routes/Application.js +13 -0
- package/src/settings/GetIncidentCategories.js +56 -0
- package/src/settings/GetIncidentTypesDetails.js +88 -0
- package/src/settings/GetIncidentTypesIds.js +74 -0
- package/src/settings/GetLocationsInService.js +54 -0
- package/src/settings/GetSingleCustomLocationDetails.js +60 -0
- package/src/settings/GetSingleIncidentTypeDetails.js +60 -0
- package/src/settings/GetTrespassReasons.js +67 -0
- package/src/settings/GetTrespassTemplates.js +51 -0
- package/src/settings/IncidentCategoriesPane.js +285 -0
- package/src/settings/IncidentCategoriesPane.test.js +229 -0
- package/src/settings/IncidentTypeDetailsPane.js +215 -0
- package/src/settings/IncidentTypeDetailsPane.test.js +220 -0
- package/src/settings/IncidentTypeEditPane.js +211 -0
- package/src/settings/IncidentTypeEditPane.test.js +170 -0
- package/src/settings/IncidentTypesPaneset.js +167 -0
- package/src/settings/IncidentTypesPaneset.test.js +124 -0
- package/src/settings/LocationInServiceEditPane.js +320 -0
- package/src/settings/LocationsPaneset.js +415 -0
- package/src/settings/LocationsPaneset.test.js +106 -0
- package/src/settings/ModalDeleteCategory.js +47 -0
- package/src/settings/ModalDeleteIncidentType.js +49 -0
- package/src/settings/ModalDeleteLocationInService.js +49 -0
- package/src/settings/ModalDeleteTrespassReason.js +49 -0
- package/src/settings/ModalPreviewTrespassDoc.js +65 -0
- package/src/settings/ModalTrespassDocTokens.js +83 -0
- package/src/settings/NewIncidentTypePane.js +182 -0
- package/src/settings/PutIncidentType.js +60 -0
- package/src/settings/PutLocationsInService.js +52 -0
- package/src/settings/PutTrespassReasons.js +61 -0
- package/src/settings/PutTrespassTemplate.js +50 -0
- package/src/settings/TrespassDoc.css +17 -0
- package/src/settings/TrespassDocDetailsPane.js +215 -0
- package/src/settings/TrespassDocEditPane.js +538 -0
- package/src/settings/TrespassDocPaneset.js +581 -0
- package/src/settings/TrespassReasonDetailsPane.js +171 -0
- package/src/settings/TrespassReasonEditPane.js +221 -0
- package/src/settings/TrespassReasonsPaneset.js +282 -0
- package/src/settings/__snapshots__/IncidentCategoriesPane.test.js.snap +3 -0
- package/src/settings/__snapshots__/IncidentTypeDetailsPane.test.js.snap +3 -0
- package/src/settings/__snapshots__/IncidentTypeEditPane.test.js.snap +3 -0
- package/src/settings/__snapshots__/IncidentTypesPaneset.test.js.snap +3 -0
- package/src/settings/__snapshots__/LocationsPaneset.test.js.snap +3 -0
- package/src/settings/data/exampleJSON.json +92 -0
- package/src/settings/data/templateTokens.js +396 -0
- package/src/settings/helpers/alphabetize.js +18 -0
- package/src/settings/helpers/getCategoryTitleById.js +13 -0
- package/src/settings/helpers/makeId.js +15 -0
- package/src/settings/index.js +48 -0
- package/stripes.config.js +10 -0
- package/test/jest/__mock__/index.js +8 -0
- package/test/jest/__mock__/intl.mock.js +27 -0
- package/test/jest/__mock__/stripes.mock.js +26 -0
- package/test/jest/__mock__/stripesComponents.mock.js +151 -0
- package/test/jest/__mock__/stripesConfig.mock.js +1 -0
- package/test/jest/__mock__/stripesCore.mock.js +9 -0
- package/test/jest/__mock__/stripesIcon.mock.js +5 -0
- package/test/jest/__mock__/stripesSmartComponents.mock.js +7 -0
- package/test/jest/__mock__/stripesUtils.mock.js +3 -0
- package/test/jest/eslintrc.js +12 -0
- package/test/jest/setupFiles.js +5 -0
- package/translations/ui-security-incident/en_US.json +542 -0
- package/ui-module-acceptance-criteria.md +34 -0
|
@@ -0,0 +1,2334 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
2
|
+
import { useIntl, FormattedMessage } from 'react-intl';
|
|
3
|
+
import { useParams, useHistory, useLocation } from 'react-router-dom';
|
|
4
|
+
import DOMPurify from 'dompurify';
|
|
5
|
+
import {
|
|
6
|
+
Accordion,
|
|
7
|
+
AccordionSet,
|
|
8
|
+
Button,
|
|
9
|
+
Checkbox,
|
|
10
|
+
Col,
|
|
11
|
+
Datepicker,
|
|
12
|
+
Editor,
|
|
13
|
+
ExpandAllButton,
|
|
14
|
+
Icon,
|
|
15
|
+
KeyValue,
|
|
16
|
+
Label,
|
|
17
|
+
List,
|
|
18
|
+
LoadingPane,
|
|
19
|
+
MessageBanner,
|
|
20
|
+
MetaSection,
|
|
21
|
+
Pane,
|
|
22
|
+
PaneHeader,
|
|
23
|
+
PaneFooter,
|
|
24
|
+
Row,
|
|
25
|
+
Select,
|
|
26
|
+
Timepicker,
|
|
27
|
+
} from '@folio/stripes/components';
|
|
28
|
+
import GetLocationsInService from '../../settings/GetLocationsInService';
|
|
29
|
+
import GetIncidentTypesDetails from '../../settings/GetIncidentTypesDetails';
|
|
30
|
+
import ModalSelectKnownCustomer from './ModalSelectKnownCustomer';
|
|
31
|
+
import ModalDescribeCustomer from './ModalDescribeCustomer';
|
|
32
|
+
import ModalSelectIncidentTypes from './ModalSelectIncidentTypes';
|
|
33
|
+
import ModalSelectWitness from './ModalSelectWitness';
|
|
34
|
+
import ModalTrespass from './ModalTrespass';
|
|
35
|
+
import ModalCustomerDetails from './ModalCustomerDetails';
|
|
36
|
+
import ModalAddMedia from './ModalAddMedia';
|
|
37
|
+
import CreateMedia from './CreateMedia';
|
|
38
|
+
import parseMMDDYYYY from './helpers/parseMMDDYYYY';
|
|
39
|
+
import convertUTCISOToPrettyDate from './helpers/convertUTCISOToPrettyDate';
|
|
40
|
+
import convertUTCISOToLocalePrettyTime from './helpers/convertUTCISOToLocalePrettyTime';
|
|
41
|
+
// formats to local date at midnight and one second in UTC ISO:
|
|
42
|
+
import formatDateToUTCISO from './helpers/formatDateToUTCISO';
|
|
43
|
+
import formatDateAndTimeToUTCISO from './helpers/formatDateAndTimeToUTCISO';
|
|
44
|
+
import isValidDateFormat from './helpers/isValidDateFormat';
|
|
45
|
+
import isValidTimeInput from './helpers/isValidTimeInput';
|
|
46
|
+
import stripHTML from './helpers/stripHTML';
|
|
47
|
+
import getTodayDate from './helpers/getTodayDate';
|
|
48
|
+
import { isSameHtml } from './helpers/isSameHtml.js';
|
|
49
|
+
import GetDetails from './GetDetails';
|
|
50
|
+
import GetSelf from './GetSelf';
|
|
51
|
+
import GetMedia from './GetMedia';
|
|
52
|
+
import GetName from './GetName';
|
|
53
|
+
import GetNameCreatedBy from './GetNameCreatedBy';
|
|
54
|
+
import identifyCurrentTrespassDocs from './helpers/identifyCurrentTrespassDocs';
|
|
55
|
+
import Thumbnail from './Thumbnail';
|
|
56
|
+
import ThumbnailSkeleton from './ThumbnailSkeleton';
|
|
57
|
+
import ThumbnailMarkRemoval from './ThumbnailMarkRemoval';
|
|
58
|
+
import ThumbnailTempPreSave from './ThumbnailTempPreSave';
|
|
59
|
+
import UpdateReport from './UpdateReport';
|
|
60
|
+
import makeId from '../../settings/helpers/makeId';
|
|
61
|
+
import ModalCustomWitness from './ModalCustomWitness';
|
|
62
|
+
import GetTrespassTemplates from '../../settings/GetTrespassTemplates';
|
|
63
|
+
import GetTrespassReasons from '../../settings/GetTrespassReasons';
|
|
64
|
+
import hasTopLevelFormChanged from './helpers/hasTopLevelFormChanged.js';
|
|
65
|
+
import hasTopLevelChangeAffectedDeclaration from './helpers/hasTopLevelChangeAffectedDeclaration.js';
|
|
66
|
+
import computeEditedCustomers from './helpers/computeEditedCustomers.js';
|
|
67
|
+
import sortTrespassDocuments from './helpers/sortTrespassDocuments.js';
|
|
68
|
+
import ModalDirtyFormWarn from './ModalDirtyFormWarn.js';
|
|
69
|
+
import ModalAttentionDecOfService from './ModalAttentionDecOfService.js';
|
|
70
|
+
import ModalLinkIncident from './ModalLinkIncident.js';
|
|
71
|
+
import GetSummary from './GetSummary.js';
|
|
72
|
+
import LinkedIncident from './LinkedIncident.js';
|
|
73
|
+
import { useIncidents } from '../../contexts/IncidentContext';
|
|
74
|
+
import {
|
|
75
|
+
generateTrespassDocumentsAtEdit,
|
|
76
|
+
generatePDFAttachments
|
|
77
|
+
} from './helpers/trespassDocUtils.js';
|
|
78
|
+
|
|
79
|
+
const EditPane = () => {
|
|
80
|
+
const history = useHistory();
|
|
81
|
+
const intl = useIntl();
|
|
82
|
+
const location = useLocation();
|
|
83
|
+
const searchRef = useRef(location.search);
|
|
84
|
+
const draftRef = useRef(''); // local unsanitized buffer - doesn't trigger React re-renders
|
|
85
|
+
const {
|
|
86
|
+
singleIncident,
|
|
87
|
+
closeEditPane,
|
|
88
|
+
openModalSelectTypes,
|
|
89
|
+
openModalUnknownCust,
|
|
90
|
+
openModalSelectKnownCust,
|
|
91
|
+
selectedCustomers, // customers selected for saving
|
|
92
|
+
setSelectedCustomers,
|
|
93
|
+
selectedWitnesses,
|
|
94
|
+
setSelectedWitnesses,
|
|
95
|
+
openModalSelectWitness,
|
|
96
|
+
self,
|
|
97
|
+
openModalTrespass,
|
|
98
|
+
isLoadingDetails,
|
|
99
|
+
openLoadingDetails,
|
|
100
|
+
isUpdatingReport,
|
|
101
|
+
setIsUpdatingReport,
|
|
102
|
+
openModalMedia,
|
|
103
|
+
setAttachmentsData, // UpdateReport passes attachmentsData to CreateMedia
|
|
104
|
+
idForMediaCreate,
|
|
105
|
+
setIdForMediaCreate,
|
|
106
|
+
formDataArrayForMediaCreate,
|
|
107
|
+
setFormDataArrayForMediaCreate,
|
|
108
|
+
openModalCustomerDetails,
|
|
109
|
+
locationsInService,
|
|
110
|
+
incidentTypesList,
|
|
111
|
+
isImageArrayLoading,
|
|
112
|
+
openImageSkeleton, // set isImageArrayLoading to true
|
|
113
|
+
// closeImageSkeleton, // set isImageArrayLoading to false
|
|
114
|
+
openModalCustomWitness,
|
|
115
|
+
trespassTemplates,
|
|
116
|
+
triggerDocumentError,
|
|
117
|
+
trespassReasons
|
|
118
|
+
} = useIncidents();
|
|
119
|
+
|
|
120
|
+
const { id } = useParams();
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (id) {
|
|
123
|
+
openLoadingDetails();
|
|
124
|
+
}
|
|
125
|
+
}, [id]);
|
|
126
|
+
|
|
127
|
+
const allowedReasonsById = useMemo(() => {
|
|
128
|
+
const list = (trespassReasons ?? []).filter(r => !r.isSuppressed);
|
|
129
|
+
return new Map(list.map(r => [r.id, r]));
|
|
130
|
+
}, [trespassReasons])
|
|
131
|
+
|
|
132
|
+
const defaultReason = useMemo(() => {
|
|
133
|
+
const r = (trespassReasons ?? []).find(x => x.isDefault && !x.isSuppressed);
|
|
134
|
+
return r ? { id: r.id, reason: r.reason } : null;
|
|
135
|
+
}, [trespassReasons]);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* - sanitizeReasons()
|
|
139
|
+
* - leveraged on any customer that is getting 'update declaration'
|
|
140
|
+
* - removes suppressed/deleted reasons (not in allowedReasonsById)
|
|
141
|
+
* - normalizes label to the current configs reason text
|
|
142
|
+
* - dedupes by id
|
|
143
|
+
* - optionally inserts a default reason if we stripped everything (shouldn't happen via UI process, but covering dark corner case...)
|
|
144
|
+
* AND the customer used to have at least one reason.
|
|
145
|
+
*/
|
|
146
|
+
const sanitizeReasons = useCallback((arr, allowedMap) => {
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const item of (arr || [])) {
|
|
150
|
+
const id = typeof item === 'string' ? item : item?.id;
|
|
151
|
+
if (!id) continue;
|
|
152
|
+
|
|
153
|
+
const allowed = allowedMap.get(id); // undefined => suppressed or deleted
|
|
154
|
+
if (!allowed) continue;
|
|
155
|
+
|
|
156
|
+
if (seen.has(id)) continue;
|
|
157
|
+
seen.add(id);
|
|
158
|
+
|
|
159
|
+
// normalize to current wording in settings
|
|
160
|
+
out.push({ id, reason: allowed.reason });
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return { sanitized: out, hadAnyBefore: (arr?.length ?? 0) > 0 };
|
|
164
|
+
}, []);
|
|
165
|
+
|
|
166
|
+
const [showModalLinkIncident, setShowModalLinkIncident] = useState(false);
|
|
167
|
+
const [linkedToSummaries, setLinkedToSummaries] = useState([]); // summaries built from 'allLinkedTo' via 'idsArray' memo
|
|
168
|
+
const [allLinkedTo, setAllLinkedTo] = useState(() => new Set()); // merging of record/selected
|
|
169
|
+
const [trespassCustomerID, setTrespassCustomerID] = useState(null);
|
|
170
|
+
const [detailsCustomerID, setDetailsCustomerID] = useState(null);
|
|
171
|
+
const [putData, setPutData] = useState({});
|
|
172
|
+
const [subLocationsDataOptions, setSubLocationsDataOptions] = useState([]);
|
|
173
|
+
const [unsavedMediaArray, setUnsavedMediaArray] = useState([]);
|
|
174
|
+
const [mediaArray, setMediaArray] = useState([]);
|
|
175
|
+
const [documents, setDocuments] = useState([]);
|
|
176
|
+
const [mostCurrentTrespassDocIds, setMostCurrentTrespassDocIds] = useState([]);
|
|
177
|
+
const [mediaSrc, setMediaSrc] = useState({});
|
|
178
|
+
const [loadingStatus, setLoadingStatus] = useState({});
|
|
179
|
+
const [removedCustomerIds, setRemovedCustomerIds] = useState([]);
|
|
180
|
+
const [removedWitnessIds, setRemovedWitnessIds] = useState([]);
|
|
181
|
+
const [associatedKeyCustArray, setAssociatedKeyCustArray] = useState([]);
|
|
182
|
+
const [associatedKeyWitArray, setAssociatedKeyWitArray] = useState([]);
|
|
183
|
+
const [allWitnesses, setAllWitnesses] = useState([]);
|
|
184
|
+
const [custWitEditObj, setCustWitEditObj] = useState({});
|
|
185
|
+
const [custWitEditID, setCustWitEditID] = useState('');
|
|
186
|
+
const [allCustomers, setAllCustomers] = useState([]);
|
|
187
|
+
const [isNoCustomer, setIsNoCustomer] = useState(false);
|
|
188
|
+
const [trespassTemplate, setTrespassTemplate] = useState('');
|
|
189
|
+
const [createdById, setCreatedById] = useState('');
|
|
190
|
+
const [updatedById, setUpdatedById] = useState('');
|
|
191
|
+
const [missingUsers, setMissingUsers] = useState([]);
|
|
192
|
+
const [stagedTrespassMap, setStagedTrespassMap] = useState(new Map()); // trespass objects set to be updated
|
|
193
|
+
const [showDirtyFormModal, setShowDirtyFormModal] = useState(false);
|
|
194
|
+
const [customersToUpdateDeclaration, setCustomersToUpdateDeclaration] = useState([]); // customers that have UI 'Update declaration' checked
|
|
195
|
+
const [isModalAttentionDecOfService, setIsModalAttentionDecOfService] = useState(false);
|
|
196
|
+
const [customersWithoutDeclaration, setCustomersWithoutDeclaration] = useState(new Set()); // persisted data customers with no declaration of service
|
|
197
|
+
const [originalDeclarationCustomerIds, setOriginalDeclarationCustomerIds] = useState(new Set()); // persisted data customers that have a declaration of service
|
|
198
|
+
const [isHydrated, setIsHydrated] = useState(false);
|
|
199
|
+
const [createdByForRender, setCreatedByForRender] = useState({
|
|
200
|
+
id: '',
|
|
201
|
+
barcode: '',
|
|
202
|
+
firstName: '',
|
|
203
|
+
lastName: ''
|
|
204
|
+
});
|
|
205
|
+
const [updatedByForRender, setUpdatedByForRender] = useState({
|
|
206
|
+
id: '',
|
|
207
|
+
barcode: '',
|
|
208
|
+
firstName: '',
|
|
209
|
+
lastName: ''
|
|
210
|
+
});
|
|
211
|
+
const [formData, setFormData] = useState({
|
|
212
|
+
customerNa: false,
|
|
213
|
+
customers: [],
|
|
214
|
+
incidentLocation: '',
|
|
215
|
+
subLocation: '',
|
|
216
|
+
dateTimeOfIncident: '', // feeds timeOfIncident key
|
|
217
|
+
timeOfIncident: '', // ui only key, does not persist in db
|
|
218
|
+
isApproximateTime: false,
|
|
219
|
+
detailedDescriptionOfIncident: '',
|
|
220
|
+
incidentWitnesses: [],
|
|
221
|
+
incidentTypes: [],
|
|
222
|
+
attachments: [],
|
|
223
|
+
id: '',
|
|
224
|
+
metadata: {},
|
|
225
|
+
staffSuppressed: undefined,
|
|
226
|
+
linkedTo: [],
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const defaultFormData = {
|
|
230
|
+
customerNa: false,
|
|
231
|
+
customers: [],
|
|
232
|
+
incidentLocation: '',
|
|
233
|
+
subLocation: '',
|
|
234
|
+
dateTimeOfIncident: '',
|
|
235
|
+
timeOfIncident: '',
|
|
236
|
+
isApproximateTime: false,
|
|
237
|
+
detailedDescriptionOfIncident: '',
|
|
238
|
+
incidentWitnesses: [],
|
|
239
|
+
incidentTypes: [],
|
|
240
|
+
attachments: [],
|
|
241
|
+
id: '',
|
|
242
|
+
metadata: {},
|
|
243
|
+
staffSuppressed: false,
|
|
244
|
+
linkedTo: [],
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const initialFormData = useMemo(() => {
|
|
248
|
+
if (!singleIncident || Object.keys(singleIncident).length === 0) {
|
|
249
|
+
return defaultFormData;
|
|
250
|
+
};
|
|
251
|
+
return {
|
|
252
|
+
customerNa: singleIncident?.customerNa,
|
|
253
|
+
customers: singleIncident?.customers || [],
|
|
254
|
+
incidentLocation: singleIncident.incidentLocation || '',
|
|
255
|
+
subLocation: singleIncident.subLocation || '',
|
|
256
|
+
dateTimeOfIncident: convertUTCISOToPrettyDate(singleIncident.dateTimeOfIncident) || '',
|
|
257
|
+
timeOfIncident: convertUTCISOToLocalePrettyTime(singleIncident.dateTimeOfIncident) || '',
|
|
258
|
+
isApproximateTime: singleIncident.isApproximateTime || false,
|
|
259
|
+
detailedDescriptionOfIncident: singleIncident.detailedDescriptionOfIncident || '',
|
|
260
|
+
incidentWitnesses: singleIncident?.incidentWitnesses || [],
|
|
261
|
+
incidentTypes: [...(singleIncident.incidentTypes || [])],
|
|
262
|
+
attachments: singleIncident.attachments || [],
|
|
263
|
+
id: singleIncident.id || '',
|
|
264
|
+
metadata: singleIncident.metadata,
|
|
265
|
+
staffSuppressed: singleIncident?.staffSuppressed || false,
|
|
266
|
+
linkedTo: singleIncident?.linkedTo || [],
|
|
267
|
+
};
|
|
268
|
+
}, [singleIncident]);
|
|
269
|
+
|
|
270
|
+
// keep track of which incident has been hydrated.
|
|
271
|
+
// prevents resetting formData every time initialFormData changes identity
|
|
272
|
+
// (e.g. modal openings). we only hydrate when incident ID
|
|
273
|
+
// changes, not when props produce a new object reference for the useEffect
|
|
274
|
+
const currentId = singleIncident?.id || '';
|
|
275
|
+
const hydratedIdRef = useRef(null);
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
if (hydratedIdRef.current !== currentId) {
|
|
278
|
+
setFormData(initialFormData);
|
|
279
|
+
setAllLinkedTo(new Set((initialFormData.linkedTo || []).map(String)));
|
|
280
|
+
setIsHydrated(true);
|
|
281
|
+
hydratedIdRef.current = currentId;
|
|
282
|
+
}
|
|
283
|
+
}, [currentId]);
|
|
284
|
+
|
|
285
|
+
const idsArray = useMemo(
|
|
286
|
+
() => Array.from(allLinkedTo).sort(),
|
|
287
|
+
[allLinkedTo]
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
if (singleIncident?.customers?.length) {
|
|
292
|
+
const ids = singleIncident.customers
|
|
293
|
+
.filter(cust => !cust.trespass?.declarationOfService)
|
|
294
|
+
.map(cust => cust.id);
|
|
295
|
+
|
|
296
|
+
setCustomersWithoutDeclaration(new Set(ids));
|
|
297
|
+
}
|
|
298
|
+
}, [singleIncident]);
|
|
299
|
+
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
if (singleIncident?.customers?.length) {
|
|
302
|
+
const ids = singleIncident.customers
|
|
303
|
+
.filter(cust => cust.trespass?.declarationOfService)
|
|
304
|
+
.map(cust => cust.id);
|
|
305
|
+
|
|
306
|
+
setOriginalDeclarationCustomerIds(new Set(ids));
|
|
307
|
+
}
|
|
308
|
+
}, [singleIncident]);
|
|
309
|
+
|
|
310
|
+
const staffSuppressedIsDirty = useMemo(
|
|
311
|
+
() => isHydrated && (formData.staffSuppressed !== initialFormData.staffSuppressed),
|
|
312
|
+
[isHydrated, formData.staffSuppressed, initialFormData.staffSuppressed]
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const topLevelIsDirty = useMemo(() => {
|
|
316
|
+
if (!isHydrated) return;
|
|
317
|
+
return hasTopLevelFormChanged(
|
|
318
|
+
formData,
|
|
319
|
+
initialFormData,
|
|
320
|
+
selectedCustomers,
|
|
321
|
+
selectedWitnesses,
|
|
322
|
+
unsavedMediaArray,
|
|
323
|
+
customersToUpdateDeclaration
|
|
324
|
+
);
|
|
325
|
+
}, [
|
|
326
|
+
isHydrated,
|
|
327
|
+
formData,
|
|
328
|
+
initialFormData,
|
|
329
|
+
selectedCustomers,
|
|
330
|
+
selectedWitnesses,
|
|
331
|
+
unsavedMediaArray,
|
|
332
|
+
customersToUpdateDeclaration
|
|
333
|
+
]);
|
|
334
|
+
|
|
335
|
+
const linkedDirty = useMemo(() => {
|
|
336
|
+
const initial = new Set((initialFormData.linkedTo || []).map(String));
|
|
337
|
+
if (initial.size !== allLinkedTo.size) return true;
|
|
338
|
+
for (const id of allLinkedTo) {
|
|
339
|
+
if (!initial.has(String(id))) return true;
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
}, [initialFormData.linkedTo, allLinkedTo]);
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
// which customers changed at the per-customer level
|
|
346
|
+
const editedCustomerIDs = useMemo(() => {
|
|
347
|
+
if (!isHydrated || !allCustomers || allCustomers.length === 0) return new Set();
|
|
348
|
+
|
|
349
|
+
return computeEditedCustomers(initialFormData, allCustomers);
|
|
350
|
+
}, [isHydrated, initialFormData.customers, allCustomers]);
|
|
351
|
+
|
|
352
|
+
const formIsDirty = useMemo(
|
|
353
|
+
() => isHydrated && (topLevelIsDirty ||
|
|
354
|
+
editedCustomerIDs.size > 0 ||
|
|
355
|
+
linkedDirty),
|
|
356
|
+
[isHydrated, topLevelIsDirty, editedCustomerIDs, linkedDirty]
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const _hasCurrentDeclaration = useCallback(
|
|
360
|
+
(cust) => Boolean(cust?.trespass?.declarationOfService),
|
|
361
|
+
[]
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const _isNewlyAddedDeclaration = useCallback(
|
|
365
|
+
(cust) => _hasCurrentDeclaration(cust) && !originalDeclarationCustomerIds.has(cust.id),
|
|
366
|
+
[originalDeclarationCustomerIds, _hasCurrentDeclaration]
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const topLevelAffectsDeclaration = useMemo(() => {
|
|
370
|
+
if (!isHydrated) return false;
|
|
371
|
+
return hasTopLevelChangeAffectedDeclaration(
|
|
372
|
+
initialFormData,
|
|
373
|
+
formData,
|
|
374
|
+
selectedWitnesses,
|
|
375
|
+
unsavedMediaArray
|
|
376
|
+
);
|
|
377
|
+
}, [
|
|
378
|
+
isHydrated,
|
|
379
|
+
initialFormData,
|
|
380
|
+
formData,
|
|
381
|
+
selectedWitnesses,
|
|
382
|
+
unsavedMediaArray
|
|
383
|
+
]);
|
|
384
|
+
|
|
385
|
+
// determine who must opt-in via 'Update declaration' if wanting persist DoS and new document generated
|
|
386
|
+
const requiredIds = useMemo(() => {
|
|
387
|
+
if (topLevelAffectsDeclaration) {
|
|
388
|
+
// global 'Update declaration' only when fields taht affect docs changed
|
|
389
|
+
return new Set(originalDeclarationCustomerIds);
|
|
390
|
+
};
|
|
391
|
+
// per-customer path
|
|
392
|
+
// only edited customers who originally had DoS
|
|
393
|
+
const set = new Set();
|
|
394
|
+
for (const id of editedCustomerIDs) {
|
|
395
|
+
if (originalDeclarationCustomerIds.has(id)) set.add(id);
|
|
396
|
+
}
|
|
397
|
+
return set;
|
|
398
|
+
}, [
|
|
399
|
+
originalDeclarationCustomerIds,
|
|
400
|
+
editedCustomerIDs,
|
|
401
|
+
topLevelAffectsDeclaration
|
|
402
|
+
]);
|
|
403
|
+
|
|
404
|
+
// who is required but not checked in list via 'update declaration'
|
|
405
|
+
const missingIds = useMemo(() => {
|
|
406
|
+
const allow = new Set(customersToUpdateDeclaration);
|
|
407
|
+
return [...requiredIds].filter(id => !allow.has(id))
|
|
408
|
+
}, [requiredIds, customersToUpdateDeclaration]);
|
|
409
|
+
|
|
410
|
+
const handleStagedTrespassUpdate = useCallback((custId, trespassData) => {
|
|
411
|
+
setStagedTrespassMap(prev => {
|
|
412
|
+
const next = new Map(prev);
|
|
413
|
+
next.set(custId, trespassData);
|
|
414
|
+
return next;
|
|
415
|
+
});
|
|
416
|
+
}, []);
|
|
417
|
+
|
|
418
|
+
const handleUpdateDeclaration = useCallback((custId) => {
|
|
419
|
+
setCustomersToUpdateDeclaration((prevArray) => {
|
|
420
|
+
if (prevArray.includes(custId)) {
|
|
421
|
+
return prevArray.filter(c => c !== custId);
|
|
422
|
+
} else {
|
|
423
|
+
return [...prevArray, custId];
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}, []);
|
|
427
|
+
|
|
428
|
+
const handleClickNo = () => {
|
|
429
|
+
handleSubmit();
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const handlClickYes = () => {
|
|
433
|
+
setIsModalAttentionDecOfService(false);
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
if (trespassTemplates) {
|
|
438
|
+
const defaultTemplate = trespassTemplates.find((template) => {
|
|
439
|
+
return template.isDefault === true;
|
|
440
|
+
});
|
|
441
|
+
if (defaultTemplate) {
|
|
442
|
+
const templateValue = defaultTemplate.templateValue
|
|
443
|
+
setTrespassTemplate(templateValue)
|
|
444
|
+
};
|
|
445
|
+
};
|
|
446
|
+
}, [trespassTemplates]);
|
|
447
|
+
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
setIsNoCustomer(formData.customerNa)
|
|
450
|
+
}, [formData.customerNa]);
|
|
451
|
+
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
if(formData.metadata && 'createdByUserId' in formData.metadata && formData.metadata.createdByUserId !== '') {
|
|
454
|
+
setCreatedById(formData.metadata.createdByUserId)
|
|
455
|
+
}
|
|
456
|
+
}, [formData, formData.metadata]);
|
|
457
|
+
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
if(formData.metadata && 'updatedByUserId' in formData.metadata && formData.metadata.updatedByUserId !== '') {
|
|
460
|
+
setUpdatedById(formData.metadata.updatedByUserId)
|
|
461
|
+
}
|
|
462
|
+
}, [formData, formData.metadata]);
|
|
463
|
+
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if(formData.attachments && formData.attachments.length > 0) {
|
|
466
|
+
const docs = formData.attachments.filter((att) => att.contentType.startsWith('application'));
|
|
467
|
+
setDocuments(docs);
|
|
468
|
+
const medias = formData.attachments.filter((att) => att.contentType.startsWith('image') || att.contentType.startsWith('video'));
|
|
469
|
+
setMediaArray(medias);
|
|
470
|
+
const newLoadingStatus = medias.reduce((acc, att) => ({
|
|
471
|
+
...acc,
|
|
472
|
+
[att.id]: loadingStatus[att.id] !== false // retain prev load status
|
|
473
|
+
? true // start as loading if not already set
|
|
474
|
+
: false
|
|
475
|
+
}), {});
|
|
476
|
+
setLoadingStatus(newLoadingStatus);
|
|
477
|
+
openImageSkeleton();
|
|
478
|
+
}
|
|
479
|
+
}, [formData.attachments])
|
|
480
|
+
|
|
481
|
+
const sortedDocuments = useMemo(() => sortTrespassDocuments(documents, mostCurrentTrespassDocIds), [documents, mostCurrentTrespassDocIds]);
|
|
482
|
+
|
|
483
|
+
const handleAddMedia = (mediaObj) => {
|
|
484
|
+
const readyMediaObj = {
|
|
485
|
+
...mediaObj,
|
|
486
|
+
id: makeId(mediaObj.description),
|
|
487
|
+
description: mediaObj.description.trim()
|
|
488
|
+
|
|
489
|
+
};
|
|
490
|
+
setUnsavedMediaArray((prev) => [
|
|
491
|
+
...prev,
|
|
492
|
+
readyMediaObj
|
|
493
|
+
]);
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const handleRemoveUnsavedMedia = (unsavedId) => {
|
|
497
|
+
const updatedUnsavedMediaArray = unsavedMediaArray.filter((obj) => obj.id !== unsavedId)
|
|
498
|
+
setUnsavedMediaArray(updatedUnsavedMediaArray);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const handleMediaUrl = (mediaUrl, attachmentId) => {
|
|
502
|
+
setMediaSrc(prev => ({ ...prev, [attachmentId]: mediaUrl }));
|
|
503
|
+
setLoadingStatus(prev => ({ ...prev, [attachmentId]: false }));
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
useEffect(() => {
|
|
507
|
+
if (documents && documents.length > 0) {
|
|
508
|
+
const startStr = 'trespass-';
|
|
509
|
+
const current = identifyCurrentTrespassDocs(documents, startStr);
|
|
510
|
+
setMostCurrentTrespassDocIds(current);
|
|
511
|
+
}
|
|
512
|
+
}, [documents]);
|
|
513
|
+
|
|
514
|
+
const thumbnailStyle = { width: '100px', height: 'auto', objectFit: 'cover'};
|
|
515
|
+
|
|
516
|
+
const handleMissingUsers = (userId) => {
|
|
517
|
+
setMissingUsers((prev) => {
|
|
518
|
+
return [ ...prev, userId]
|
|
519
|
+
})
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const handleCloseEdit = () => {
|
|
523
|
+
setCreatedByForRender({
|
|
524
|
+
id: '',
|
|
525
|
+
barcode: '',
|
|
526
|
+
firstName: '',
|
|
527
|
+
lastName: ''
|
|
528
|
+
});
|
|
529
|
+
setUpdatedByForRender({
|
|
530
|
+
id: '',
|
|
531
|
+
barcode: '',
|
|
532
|
+
firstName: '',
|
|
533
|
+
lastName: ''
|
|
534
|
+
});
|
|
535
|
+
setCreatedById('');
|
|
536
|
+
setUpdatedById('');
|
|
537
|
+
setSelectedCustomers([]);
|
|
538
|
+
setSelectedWitnesses([]);
|
|
539
|
+
setMissingUsers([]);
|
|
540
|
+
setIdForMediaCreate(null);
|
|
541
|
+
setFormDataArrayForMediaCreate(null);
|
|
542
|
+
setCustomersToUpdateDeclaration([]);
|
|
543
|
+
closeEditPane();
|
|
544
|
+
history.replace(`/incidents/${id}${searchRef.current}`);
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const anyDirty = formIsDirty || staffSuppressedIsDirty;
|
|
548
|
+
const handleClickDismissCancel = () => {
|
|
549
|
+
if (anyDirty) {
|
|
550
|
+
setShowDirtyFormModal(true);
|
|
551
|
+
} else {
|
|
552
|
+
handleCloseEdit();
|
|
553
|
+
};
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const handleKeepEditing = () => {
|
|
557
|
+
setShowDirtyFormModal(false);
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const handleDismissOnDirty = () => {
|
|
561
|
+
handleCloseEdit();
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const clearSubLocation = () => {
|
|
565
|
+
setFormData({
|
|
566
|
+
...formData,
|
|
567
|
+
subLocation: '',
|
|
568
|
+
});
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const handleOpenModalLinkIncident = () => {
|
|
572
|
+
setShowModalLinkIncident(true)
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const handleCloseModalLinkIncident = () => {
|
|
576
|
+
setShowModalLinkIncident(false)
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const toggleRowChecked = useCallback((id) => {
|
|
580
|
+
setAllLinkedTo(prev => {
|
|
581
|
+
const nextSet = new Set(prev);
|
|
582
|
+
nextSet.has(id) ? nextSet.delete(id) : nextSet.add(id);
|
|
583
|
+
return nextSet;
|
|
584
|
+
})
|
|
585
|
+
}, []);
|
|
586
|
+
|
|
587
|
+
const handleTrashLinkedIncident = (toDeleteId) => {
|
|
588
|
+
toggleRowChecked(toDeleteId)
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const locationDataOptions = useMemo(() => {
|
|
592
|
+
const defaultValueLabel = [{
|
|
593
|
+
value: '',
|
|
594
|
+
label: <FormattedMessage
|
|
595
|
+
id="create-pane.locationDataOptions-label-select-location"/>
|
|
596
|
+
}];
|
|
597
|
+
const formattedLocations = locationsInService
|
|
598
|
+
? locationsInService.map((loc) => ({
|
|
599
|
+
// <Select /> to match current inst loc.id to pretty name
|
|
600
|
+
value: loc.id,
|
|
601
|
+
label: loc.location,
|
|
602
|
+
subLocations: loc.subLocations ? loc.subLocations : []
|
|
603
|
+
}))
|
|
604
|
+
: [{
|
|
605
|
+
value: '',
|
|
606
|
+
label: <FormattedMessage
|
|
607
|
+
id="create-pane.locationDataOptions-label-no-loaded"/>
|
|
608
|
+
}];
|
|
609
|
+
return [
|
|
610
|
+
...defaultValueLabel,
|
|
611
|
+
...formattedLocations,
|
|
612
|
+
];
|
|
613
|
+
}, [locationsInService]);
|
|
614
|
+
|
|
615
|
+
const runSubLocationsSelect = useCallback((value) => {
|
|
616
|
+
let subLocs;
|
|
617
|
+
let options;
|
|
618
|
+
const noSubLocationOption = [{
|
|
619
|
+
value: 'No sub-location',
|
|
620
|
+
label: <FormattedMessage
|
|
621
|
+
id="create-pane.subLocations-label-default-no-sub-location"/>
|
|
622
|
+
}];
|
|
623
|
+
const noValueLabel = [{
|
|
624
|
+
value: '',
|
|
625
|
+
label: <FormattedMessage
|
|
626
|
+
id="create-pane.subLocations-label-no-sub-location-available"
|
|
627
|
+
/>
|
|
628
|
+
}];
|
|
629
|
+
|
|
630
|
+
// ensure initialization of locationDataOptions
|
|
631
|
+
const currentValue = Array.isArray(locationDataOptions)
|
|
632
|
+
? locationDataOptions.find((loc) => loc.value === value)
|
|
633
|
+
: undefined;
|
|
634
|
+
|
|
635
|
+
// ensure initialization of currentValue.subLocations
|
|
636
|
+
if (currentValue && Array.isArray(currentValue.subLocations) && currentValue.subLocations.length > 0) {
|
|
637
|
+
subLocs = currentValue.subLocations.map((sub) => {
|
|
638
|
+
return { value: sub.name, label: `${sub.name} - ${sub.description}` };
|
|
639
|
+
});
|
|
640
|
+
options = [...noSubLocationOption, ...subLocs];
|
|
641
|
+
} else {
|
|
642
|
+
options = [...noValueLabel];
|
|
643
|
+
};
|
|
644
|
+
setSubLocationsDataOptions(options);
|
|
645
|
+
}, [locationDataOptions]);
|
|
646
|
+
|
|
647
|
+
useEffect(() => {
|
|
648
|
+
// for initial rendering the current instance subLocation value in Select
|
|
649
|
+
// dependency of once locationDataOptions has been set
|
|
650
|
+
runSubLocationsSelect(formData.incidentLocation);
|
|
651
|
+
}, [locationDataOptions, formData.incidentLocation, runSubLocationsSelect]);
|
|
652
|
+
|
|
653
|
+
const handleChange = (eventOrValue) => {
|
|
654
|
+
let name;
|
|
655
|
+
let value;
|
|
656
|
+
setFormData((prev) => ({
|
|
657
|
+
...prev,
|
|
658
|
+
[name]: value,
|
|
659
|
+
}));
|
|
660
|
+
if (eventOrValue && eventOrValue.target) {
|
|
661
|
+
({ name, value } = eventOrValue.target);
|
|
662
|
+
|
|
663
|
+
if (eventOrValue.target.type === 'checkbox') {
|
|
664
|
+
value = eventOrValue.target.checked;
|
|
665
|
+
}
|
|
666
|
+
} else {
|
|
667
|
+
// if no 'target' property in custom component such as
|
|
668
|
+
// ( has array of selected options, not event object)
|
|
669
|
+
name = 'incidentWitnesses';
|
|
670
|
+
value = eventOrValue;
|
|
671
|
+
}
|
|
672
|
+
if (
|
|
673
|
+
name === 'customerDetails.firstName' ||
|
|
674
|
+
name === 'customerDetails.lastName'
|
|
675
|
+
) {
|
|
676
|
+
const key = name.split('.')[1];
|
|
677
|
+
|
|
678
|
+
setFormData((prev) => ({
|
|
679
|
+
...prev,
|
|
680
|
+
customerDetails: {
|
|
681
|
+
...prev.customerDetails,
|
|
682
|
+
[key]: value,
|
|
683
|
+
},
|
|
684
|
+
}));
|
|
685
|
+
}
|
|
686
|
+
if (name === 'incidentWitnesses') {
|
|
687
|
+
const selectedRoles = value.map((item) => item.value);
|
|
688
|
+
const updatedWitnesses = formData.incidentWitnesses.map((witness) => ({
|
|
689
|
+
...witness,
|
|
690
|
+
selected: selectedRoles.includes(witness.role),
|
|
691
|
+
}));
|
|
692
|
+
setFormData((prev) => ({
|
|
693
|
+
...prev,
|
|
694
|
+
incidentWitnesses: updatedWitnesses,
|
|
695
|
+
}));
|
|
696
|
+
}
|
|
697
|
+
if (name === 'incidentLocation') {
|
|
698
|
+
clearSubLocation();
|
|
699
|
+
runSubLocationsSelect(value);
|
|
700
|
+
setFormData((prev) => ({
|
|
701
|
+
...prev,
|
|
702
|
+
[name]: value,
|
|
703
|
+
}));
|
|
704
|
+
}
|
|
705
|
+
if (name === 'dateTimeOfIncident') {
|
|
706
|
+
setFormData((prev) => ({
|
|
707
|
+
...prev,
|
|
708
|
+
[name]: value,
|
|
709
|
+
}));
|
|
710
|
+
} else {
|
|
711
|
+
setFormData((prev) => ({
|
|
712
|
+
...prev,
|
|
713
|
+
[name]: value,
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
useEffect(() => {
|
|
719
|
+
draftRef.current = formData.detailedDescriptionOfIncident || '';
|
|
720
|
+
}, [formData.detailedDescriptionOfIncident])
|
|
721
|
+
|
|
722
|
+
// fires on every key-press but only mutates the ref
|
|
723
|
+
const handleDescriptionChange = (content) => {
|
|
724
|
+
draftRef.current = content;
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// commits once, with sanitized HTML
|
|
728
|
+
const handleEditorBlur = () => {
|
|
729
|
+
const sanitizedContent = DOMPurify.sanitize(draftRef.current);
|
|
730
|
+
setFormData(prev =>
|
|
731
|
+
isSameHtml(prev.detailedDescriptionOfIncident, sanitizedContent)
|
|
732
|
+
? prev
|
|
733
|
+
: { ...prev, detailedDescriptionOfIncident: sanitizedContent }
|
|
734
|
+
);
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
const handleEditorKeyDown = (e) => {
|
|
738
|
+
if (e.key === 'Tab') {
|
|
739
|
+
handleEditorBlur(); // force commit when user tabs out
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
const handleGetWitnessName = (witObj) => {
|
|
744
|
+
setAssociatedKeyWitArray((prevState) => {
|
|
745
|
+
return [...prevState, witObj]
|
|
746
|
+
})
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
useEffect(() => {
|
|
750
|
+
if(associatedKeyWitArray && associatedKeyWitArray.length > 0) {
|
|
751
|
+
const readyDataWitnesses = formData.incidentWitnesses.map((dataObj) => {
|
|
752
|
+
const matchingWit = associatedKeyWitArray.find(
|
|
753
|
+
(wit) => wit.id === dataObj.id
|
|
754
|
+
);
|
|
755
|
+
if(matchingWit) {
|
|
756
|
+
return {
|
|
757
|
+
...dataObj,
|
|
758
|
+
associatedFirstName: matchingWit.firstName,
|
|
759
|
+
associatedLastName: matchingWit.lastName
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
return dataObj;
|
|
763
|
+
});
|
|
764
|
+
setFormData(prev => ({
|
|
765
|
+
...prev,
|
|
766
|
+
incidentWitnesses: readyDataWitnesses
|
|
767
|
+
}))
|
|
768
|
+
}
|
|
769
|
+
}, [associatedKeyWitArray]);
|
|
770
|
+
|
|
771
|
+
useEffect(() => {
|
|
772
|
+
const witnessesSet = new Set(
|
|
773
|
+
[...formData.incidentWitnesses, ...selectedWitnesses].map((wit) =>
|
|
774
|
+
JSON.stringify(wit)
|
|
775
|
+
)
|
|
776
|
+
);
|
|
777
|
+
const mergedNoDuplicateWitnesses = Array.from(witnessesSet).map((wit) =>
|
|
778
|
+
JSON.parse(wit)
|
|
779
|
+
);
|
|
780
|
+
setAllWitnesses(mergedNoDuplicateWitnesses)
|
|
781
|
+
}, [formData.incidentWitnesses, selectedWitnesses]);
|
|
782
|
+
|
|
783
|
+
const handleGetCustName = (userObj) => {
|
|
784
|
+
setAssociatedKeyCustArray((prevState) => {
|
|
785
|
+
return [...prevState, userObj];
|
|
786
|
+
})
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
useEffect(() => {
|
|
790
|
+
// handle non registered customers to bypass setting associated key names
|
|
791
|
+
singleIncident.customers?.forEach((cust) => {
|
|
792
|
+
if (cust.registered === false) {
|
|
793
|
+
handleGetCustName(cust);
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
}, [singleIncident.customers]);
|
|
797
|
+
|
|
798
|
+
useEffect(() => {
|
|
799
|
+
// handle registered customers to add in temporary key/values for rendering name based on associated keys
|
|
800
|
+
if(associatedKeyCustArray && associatedKeyCustArray.length > 0) {
|
|
801
|
+
const readyDataCustomers = formData.customers.map((dataObj) => {
|
|
802
|
+
const matchingCust = associatedKeyCustArray.find(
|
|
803
|
+
(cust) => cust.id === dataObj.id
|
|
804
|
+
);
|
|
805
|
+
if (matchingCust) {
|
|
806
|
+
return {
|
|
807
|
+
...dataObj,
|
|
808
|
+
associatedFirstName: matchingCust.firstName,
|
|
809
|
+
associatedLastName: matchingCust.lastName
|
|
810
|
+
};
|
|
811
|
+
};
|
|
812
|
+
return dataObj;
|
|
813
|
+
});
|
|
814
|
+
setFormData(prev => ({
|
|
815
|
+
...prev,
|
|
816
|
+
customers: readyDataCustomers
|
|
817
|
+
}))
|
|
818
|
+
};
|
|
819
|
+
}, [associatedKeyCustArray]);
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
/*
|
|
823
|
+
effect ensures that allCustomers always refelcts the most recently
|
|
824
|
+
edited customer data.
|
|
825
|
+
it merges customer info from prevAllCustomers, formData.customers, and selectedCustomers,
|
|
826
|
+
always preferring latest version for each customer (formData > selected > prev).
|
|
827
|
+
only the trespass field is conditionally updated from stagedTrespassMap.
|
|
828
|
+
this prevents stale data from overwriting recent edits,
|
|
829
|
+
especially after ModalTrespass.js save/submit (fix: which previously fired downstream
|
|
830
|
+
events to unexpectedly wipe ModalCustomerDetails.js edits).
|
|
831
|
+
*/
|
|
832
|
+
useEffect(() => {
|
|
833
|
+
if (!isHydrated) return;
|
|
834
|
+
setAllCustomers(prevAllCustomers => {
|
|
835
|
+
// previous state, keyed by id
|
|
836
|
+
const prevMap = new Map(prevAllCustomers.map(c => [c.id, c]));
|
|
837
|
+
// build the new list de-duped by id, while merging fields
|
|
838
|
+
const byId = new Map();
|
|
839
|
+
|
|
840
|
+
// helper to merge staged trespass, but preserve all other fields from prevAllCustomers
|
|
841
|
+
const mergeOne = (cust) => {
|
|
842
|
+
if (!cust || !cust.id) return;
|
|
843
|
+
const existing = prevMap.get(cust.id) || cust;
|
|
844
|
+
const stagedTrespass = stagedTrespassMap.get(cust.id);
|
|
845
|
+
|
|
846
|
+
// only update trespass if staged otherwise keep everything else as last edited
|
|
847
|
+
const merged = {
|
|
848
|
+
...existing,
|
|
849
|
+
trespass: stagedTrespass !== undefined ? stagedTrespass : existing.trespass,
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
byId.set(cust.id, merged);
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// always use the union of all customer IDs from previous state, formData, and selectedCustomers
|
|
856
|
+
const allIds = new Set([
|
|
857
|
+
...prevAllCustomers.map(c => c.id),
|
|
858
|
+
...formData.customers.map(c => c.id),
|
|
859
|
+
...selectedCustomers.map(c => c.id),
|
|
860
|
+
]);
|
|
861
|
+
|
|
862
|
+
// for each customer, merge as above
|
|
863
|
+
allIds.forEach(id => {
|
|
864
|
+
// prefer prevAllCustomers as the base
|
|
865
|
+
const prev = prevMap.get(id);
|
|
866
|
+
// find the latest in formData or selectedCustomers if present
|
|
867
|
+
const formCust = formData.customers.find(c => c.id === id);
|
|
868
|
+
const selectedCust = selectedCustomers.find(c => c.id === id);
|
|
869
|
+
// use the most recently edited customer object, falling back to prev
|
|
870
|
+
const baseCust = formCust || selectedCust || prev;
|
|
871
|
+
mergeOne(baseCust);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
return Array.from(byId.values());
|
|
875
|
+
});
|
|
876
|
+
}, [isHydrated, formData.customers, selectedCustomers, stagedTrespassMap]);
|
|
877
|
+
|
|
878
|
+
const handleRemoveCustomer = (customerId) => {
|
|
879
|
+
setFormData(prevFormData => ({
|
|
880
|
+
...prevFormData,
|
|
881
|
+
customers: prevFormData.customers.filter(cust => cust.id !== customerId)
|
|
882
|
+
}))
|
|
883
|
+
setSelectedCustomers(prevSelectedCustomers =>
|
|
884
|
+
prevSelectedCustomers.filter(cust => cust.id !== customerId)
|
|
885
|
+
);
|
|
886
|
+
setRemovedCustomerIds(prev => [...prev, customerId]);
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// persisted (original) customers on the incident
|
|
890
|
+
const initialCustomerIds = useMemo(
|
|
891
|
+
() => new Set((initialFormData.customers ?? []).map(c => c.id)),
|
|
892
|
+
[initialFormData.customers]
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
// truly new in THIS edit session (not present in initial incident)
|
|
896
|
+
const newlyAddedCustomerIds = useMemo(() => {
|
|
897
|
+
const out = new Set();
|
|
898
|
+
for (const c of allCustomers) {
|
|
899
|
+
if (c?.id && !initialCustomerIds.has(c.id)) out.add(c.id);
|
|
900
|
+
}
|
|
901
|
+
return out;
|
|
902
|
+
}, [allCustomers, initialCustomerIds]);
|
|
903
|
+
|
|
904
|
+
const optedSet = useMemo(
|
|
905
|
+
() => new Set(customersToUpdateDeclaration),
|
|
906
|
+
[customersToUpdateDeclaration]
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
const isNewlyAddedDoS = (cust) =>
|
|
910
|
+
Boolean(cust.trespass?.declarationOfService) &&
|
|
911
|
+
!originalDeclarationCustomerIds.has(cust.id);
|
|
912
|
+
|
|
913
|
+
const willReceiveNewDeclaration = (cust) => {
|
|
914
|
+
const hasDoSNow = Boolean(cust.trespass?.declarationOfService);
|
|
915
|
+
return (
|
|
916
|
+
hasDoSNow &&
|
|
917
|
+
!formData.staffSuppressed &&
|
|
918
|
+
(
|
|
919
|
+
optedSet.has(cust.id) || // user opted-in
|
|
920
|
+
newlyAddedCustomerIds.has(cust.id) || // truly new & with DoS
|
|
921
|
+
isNewlyAddedDoS(cust) // DoS added this edit
|
|
922
|
+
)
|
|
923
|
+
);
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
const finalizeSubmission = async (finalCustomersArray) => {
|
|
927
|
+
/*
|
|
928
|
+
For 'detailedDescriptionOfIncident' build that value from draftRef,
|
|
929
|
+
not formData, so we always send the latest text even if user clicks save
|
|
930
|
+
while the Editor still has focus and onBlur hasn't committed the field to state yet.
|
|
931
|
+
Note that 'detailedDescriptionOfIncident' feeds a trespass object's 'descriptionOfOccurrence' value, which is only seen in the context of raw record or an automated trespass document if that token is chosen for the doc template.
|
|
932
|
+
*/
|
|
933
|
+
const cleanedDetailedDescription = DOMPurify.sanitize(draftRef.current);
|
|
934
|
+
// console.log("cleanedDetailedDescription --> ", JSON.stringify(cleanedDetailedDescription, null, 2))
|
|
935
|
+
|
|
936
|
+
try {
|
|
937
|
+
// remove temp associated name keys
|
|
938
|
+
const formattedWitnesses = allWitnesses.map(({ associatedFirstName, associatedLastName, ...rest }) => {
|
|
939
|
+
if (rest.isCustom) {
|
|
940
|
+
const { role, phone, email, ...others } = rest;
|
|
941
|
+
|
|
942
|
+
const trimmedRole = rest.role?.trim();
|
|
943
|
+
const trimmedPhone = rest.phone?.trim();
|
|
944
|
+
const trimmedEmail = rest.email?.trim();
|
|
945
|
+
return {
|
|
946
|
+
...others,
|
|
947
|
+
...(trimmedRole ? { role: trimmedRole } : {}),
|
|
948
|
+
...(trimmedPhone ? { phone: trimmedPhone } : {}),
|
|
949
|
+
...(trimmedEmail ? { email: trimmedEmail } : {})
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
return rest;
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
// let for conditional assignment
|
|
956
|
+
let formattedCustomers = finalCustomersArray.map((cust) => {
|
|
957
|
+
let updatedCustomer = { ...cust };
|
|
958
|
+
delete updatedCustomer.associatedFirstName;
|
|
959
|
+
delete updatedCustomer.associatedLastName;
|
|
960
|
+
|
|
961
|
+
if (cust.trespass) {
|
|
962
|
+
let reasonPatch = {};
|
|
963
|
+
if (willReceiveNewDeclaration(cust)) {
|
|
964
|
+
const currentReasons = cust.trespass.exclusionOrTrespassBasedOn || [];
|
|
965
|
+
const { sanitized: cleaned, hadAnyBefore } =
|
|
966
|
+
sanitizeReasons(currentReasons, allowedReasonsById);
|
|
967
|
+
|
|
968
|
+
const finalReasons =
|
|
969
|
+
cleaned.length > 0
|
|
970
|
+
? cleaned
|
|
971
|
+
: (hadAnyBefore && defaultReason ? [defaultReason] : []);
|
|
972
|
+
|
|
973
|
+
reasonPatch = { exclusionOrTrespassBasedOn: finalReasons };
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
let trespassDesc = cust.trespass.description?.trim();
|
|
977
|
+
updatedCustomer = {
|
|
978
|
+
...updatedCustomer,
|
|
979
|
+
trespass: {
|
|
980
|
+
...cust.trespass,
|
|
981
|
+
...reasonPatch, // only applied when gated in, otherise untouched
|
|
982
|
+
dateOfOccurrence: formatDateToUTCISO(formData.dateTimeOfIncident),
|
|
983
|
+
...(cust.trespass.endDateOfTrespass
|
|
984
|
+
? {
|
|
985
|
+
endDateOfTrespass: formatDateToUTCISO(
|
|
986
|
+
cust.trespass.endDateOfTrespass
|
|
987
|
+
),
|
|
988
|
+
}
|
|
989
|
+
: {}),
|
|
990
|
+
...(cust.trespass.declarationOfService
|
|
991
|
+
? {
|
|
992
|
+
declarationOfService: {
|
|
993
|
+
...cust.trespass.declarationOfService,
|
|
994
|
+
date: formatDateToUTCISO(
|
|
995
|
+
cust.trespass.declarationOfService.date
|
|
996
|
+
),
|
|
997
|
+
},
|
|
998
|
+
}
|
|
999
|
+
: {}),
|
|
1000
|
+
|
|
1001
|
+
// descriptionOfOccurrence: cleanedDetailedDescription.trim(),
|
|
1002
|
+
// if custom trespass description exists, use it for both fields
|
|
1003
|
+
...(trespassDesc
|
|
1004
|
+
? {
|
|
1005
|
+
description: DOMPurify.sanitize(trespassDesc),
|
|
1006
|
+
descriptionOfOccurrence: DOMPurify.sanitize(trespassDesc),
|
|
1007
|
+
}
|
|
1008
|
+
: {
|
|
1009
|
+
// default
|
|
1010
|
+
descriptionOfOccurrence: cleanedDetailedDescription.trim(),
|
|
1011
|
+
}),
|
|
1012
|
+
witnessedBy: formattedWitnesses,
|
|
1013
|
+
},
|
|
1014
|
+
};
|
|
1015
|
+
};
|
|
1016
|
+
if (cust.details) {
|
|
1017
|
+
const { dateOfBirth, ...restDetails } = cust.details;
|
|
1018
|
+
updatedCustomer = {
|
|
1019
|
+
...updatedCustomer,
|
|
1020
|
+
details: {
|
|
1021
|
+
...restDetails,
|
|
1022
|
+
...(dateOfBirth ?
|
|
1023
|
+
{ dateOfBirth: formatDateToUTCISO(cust.details.dateOfBirth) }
|
|
1024
|
+
: {}),
|
|
1025
|
+
},
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
return updatedCustomer;
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
const updatedMetadata = {
|
|
1032
|
+
createdByUserId: singleIncident.metadata.createdByUserId,
|
|
1033
|
+
createdDate: singleIncident.metadata.createdDate,
|
|
1034
|
+
updatedByUserId: self.id
|
|
1035
|
+
};
|
|
1036
|
+
if (formData.customerNa && formattedCustomers.length > 0) {
|
|
1037
|
+
formattedCustomers = []
|
|
1038
|
+
};
|
|
1039
|
+
const data = {
|
|
1040
|
+
...formData,
|
|
1041
|
+
detailedDescriptionOfIncident: cleanedDetailedDescription,
|
|
1042
|
+
customerNa: formattedCustomers.length > 0 ? false : formData.customerNa,
|
|
1043
|
+
customers: formData.customerNa ? [] : formattedCustomers,
|
|
1044
|
+
dateTimeOfIncident: formatDateAndTimeToUTCISO(formData.dateTimeOfIncident, formData.timeOfIncident),
|
|
1045
|
+
incidentWitnesses: formattedWitnesses, // allWitnesses witnessesList
|
|
1046
|
+
id: singleIncident.id,
|
|
1047
|
+
createdBy: singleIncident.createdBy,
|
|
1048
|
+
metadata: updatedMetadata,
|
|
1049
|
+
linkedTo: Array.from(allLinkedTo)
|
|
1050
|
+
};
|
|
1051
|
+
delete data.timeOfIncident; // is temp UI render key and its value derives from dateTimeOfIncident
|
|
1052
|
+
|
|
1053
|
+
const onlyLinkedChanged = linkedDirty &&
|
|
1054
|
+
!topLevelAffectsDeclaration &&
|
|
1055
|
+
editedCustomerIDs.size === 0;
|
|
1056
|
+
|
|
1057
|
+
let readyTrespassDocuments = [];
|
|
1058
|
+
let trespassDocumentPDFs = [];
|
|
1059
|
+
// only generate trespass PDFs if record is not staffSuppressed and not only did linkedTo change
|
|
1060
|
+
if (!formData.staffSuppressed && !onlyLinkedChanged) {
|
|
1061
|
+
// generate trespass documents
|
|
1062
|
+
try {
|
|
1063
|
+
const helperDeps = { locationDataOptions, trespassReasons, self, triggerDocumentError};
|
|
1064
|
+
const selectedCustomerIds = new Set(selectedCustomers.map(c => c.id));
|
|
1065
|
+
|
|
1066
|
+
readyTrespassDocuments = generateTrespassDocumentsAtEdit(
|
|
1067
|
+
formattedCustomers,
|
|
1068
|
+
customersToUpdateDeclaration, // array
|
|
1069
|
+
selectedCustomerIds, // Set
|
|
1070
|
+
originalDeclarationCustomerIds, // Set
|
|
1071
|
+
// editedCustomerIDs, // Set
|
|
1072
|
+
topLevelAffectsDeclaration, // boolean
|
|
1073
|
+
data,
|
|
1074
|
+
trespassTemplate,
|
|
1075
|
+
helperDeps
|
|
1076
|
+
);
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
console.error(`Error at readyTrespassDocuments: ${error}`);
|
|
1079
|
+
// triggerDocumentError(`Error in generateTrespassDocuments: ${error}`)
|
|
1080
|
+
const errorMsg = error.message;
|
|
1081
|
+
triggerDocumentError(<FormattedMessage
|
|
1082
|
+
id="generate-trespass.error-doc-readyTrespassDocuments"
|
|
1083
|
+
values={{ error: errorMsg }}
|
|
1084
|
+
/>)
|
|
1085
|
+
readyTrespassDocuments = [];
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
try {
|
|
1089
|
+
// trespassDocumentPDFs = await generatePDFAttachments();
|
|
1090
|
+
trespassDocumentPDFs = await generatePDFAttachments(
|
|
1091
|
+
readyTrespassDocuments,
|
|
1092
|
+
triggerDocumentError
|
|
1093
|
+
);
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
console.error(`Unexpected error in PDF generation: ${error}`);
|
|
1096
|
+
// triggerDocumentError(`Unexpected error in PDF generation: ${error}`)
|
|
1097
|
+
const errorMsg = error.message;
|
|
1098
|
+
triggerDocumentError(<FormattedMessage
|
|
1099
|
+
id="generate-trespass.error-doc-unexpected-error"
|
|
1100
|
+
values={{ error: errorMsg }}
|
|
1101
|
+
/>)
|
|
1102
|
+
trespassDocumentPDFs = [];
|
|
1103
|
+
};
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
const readyToBeSaved = unsavedMediaArray.map((mediaObj) => {
|
|
1107
|
+
const {id, file, description, contentType} = mediaObj
|
|
1108
|
+
return {
|
|
1109
|
+
contentType: contentType,
|
|
1110
|
+
description: description,
|
|
1111
|
+
id: id,
|
|
1112
|
+
file: file,
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
const mergedAttachments = [...readyToBeSaved, ...trespassDocumentPDFs];
|
|
1116
|
+
// UpdateReport will pass attachmentsData to CreateMedia on PUT success
|
|
1117
|
+
setAttachmentsData(mergedAttachments);
|
|
1118
|
+
// console.log('@Edit - the PUT data: ', JSON.stringify(data, null, 2));
|
|
1119
|
+
setPutData(data);
|
|
1120
|
+
setSelectedCustomers([]);
|
|
1121
|
+
setSelectedWitnesses([]);
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
console.error('error in edit submit - error: ', error)
|
|
1124
|
+
};
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
const cleanupCustomerDeclarations = () => {
|
|
1128
|
+
const allowSet = new Set(customersToUpdateDeclaration);
|
|
1129
|
+
const selectedCustomerIds = new Set(selectedCustomers.map(c => c.id));
|
|
1130
|
+
// const wiped = [];
|
|
1131
|
+
|
|
1132
|
+
const updated = allCustomers.map((cust) => {
|
|
1133
|
+
const hasDoS = _hasCurrentDeclaration(cust);
|
|
1134
|
+
if (!hasDoS) return cust;
|
|
1135
|
+
|
|
1136
|
+
// const originallyHad = originalDeclarationCustomerIds.has(cust.id);
|
|
1137
|
+
const newlyAdded = _isNewlyAddedDeclaration(cust); // at CURRENT time
|
|
1138
|
+
const edited = editedCustomerIDs.has(cust.id);
|
|
1139
|
+
|
|
1140
|
+
const keep =
|
|
1141
|
+
allowSet.has(cust.id) || // explicitly opted-in
|
|
1142
|
+
selectedCustomerIds.has(cust.id) || // new customer this session
|
|
1143
|
+
newlyAdded || // newly added DoS
|
|
1144
|
+
(!topLevelAffectsDeclaration && !edited); // only document-affecting globals
|
|
1145
|
+
|
|
1146
|
+
if (keep) return cust;
|
|
1147
|
+
|
|
1148
|
+
const clone = { ...cust, trespass: { ...cust.trespass } };
|
|
1149
|
+
delete clone.trespass.declarationOfService;
|
|
1150
|
+
|
|
1151
|
+
return clone;
|
|
1152
|
+
});
|
|
1153
|
+
return updated;
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
const handleSaveAndCloseClick = () => {
|
|
1157
|
+
// staffSuppressed only path
|
|
1158
|
+
if (staffSuppressedIsDirty && !formIsDirty) {
|
|
1159
|
+
handleSubmit();
|
|
1160
|
+
return;
|
|
1161
|
+
};
|
|
1162
|
+
if (isNoCustomer) {
|
|
1163
|
+
handleSubmit();
|
|
1164
|
+
return;
|
|
1165
|
+
};
|
|
1166
|
+
if (missingIds.length > 0) {
|
|
1167
|
+
setIsModalAttentionDecOfService(true);
|
|
1168
|
+
return;
|
|
1169
|
+
};
|
|
1170
|
+
// good to go, no exceptions/warns happy path
|
|
1171
|
+
handleSubmit();
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
const handleSubmit = () => {
|
|
1175
|
+
setIsUpdatingReport(true);
|
|
1176
|
+
const readyCustomers = formData.staffSuppressed
|
|
1177
|
+
? allCustomers
|
|
1178
|
+
: cleanupCustomerDeclarations();
|
|
1179
|
+
finalizeSubmission(readyCustomers);
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
const dateIsNotInFuture = (dateString) => {
|
|
1183
|
+
const todayString = getTodayDate();
|
|
1184
|
+
const todayDate = parseMMDDYYYY(todayString);
|
|
1185
|
+
todayDate.setHours(0,0,0,0);
|
|
1186
|
+
const formDataDate = parseMMDDYYYY(dateString);
|
|
1187
|
+
if (formDataDate <= todayDate) {
|
|
1188
|
+
return true
|
|
1189
|
+
} else if (formDataDate > todayDate){
|
|
1190
|
+
return false
|
|
1191
|
+
};
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
const isFormDataValid = () => {
|
|
1195
|
+
const isCustomersValid =
|
|
1196
|
+
(allCustomers.length > 0 && !formData.customerNa) || formData.customerNa;
|
|
1197
|
+
const isIncidentDetailsValid =
|
|
1198
|
+
formData.incidentLocation !== '' &&
|
|
1199
|
+
isValidDateFormat(formData.dateTimeOfIncident) &&
|
|
1200
|
+
dateIsNotInFuture(formData.dateTimeOfIncident) &&
|
|
1201
|
+
stripHTML(formData.detailedDescriptionOfIncident) !== '' &&
|
|
1202
|
+
isValidTimeInput(formData.timeOfIncident);
|
|
1203
|
+
const isIncidentTypeValid = formData.incidentTypes.length > 0;
|
|
1204
|
+
const isWitnessValid = allWitnesses && allWitnesses.length > 0;
|
|
1205
|
+
return (
|
|
1206
|
+
isCustomersValid &&
|
|
1207
|
+
isIncidentDetailsValid &&
|
|
1208
|
+
isIncidentTypeValid &&
|
|
1209
|
+
isWitnessValid
|
|
1210
|
+
);
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
const handleGetCreatedByName = (createdByNameObj) => {
|
|
1214
|
+
if (createdByNameObj) {
|
|
1215
|
+
// console.log("@handleGetCreatedByName - createdByNameObj: ", JSON.stringify(createdByNameObj, null, 2))
|
|
1216
|
+
setCreatedByForRender({
|
|
1217
|
+
id: createdByNameObj.id,
|
|
1218
|
+
barcode: createdByNameObj.barcode,
|
|
1219
|
+
firstName: createdByNameObj.firstName,
|
|
1220
|
+
lastName: createdByNameObj.lastName
|
|
1221
|
+
})
|
|
1222
|
+
} else {
|
|
1223
|
+
setCreatedByForRender({
|
|
1224
|
+
id: '',
|
|
1225
|
+
barcode: '',
|
|
1226
|
+
firstName: '',
|
|
1227
|
+
lastName: ''
|
|
1228
|
+
})
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
const handleGetUpdatedByName = (updatedByNameObj) => {
|
|
1233
|
+
if (updatedByNameObj) {
|
|
1234
|
+
setUpdatedByForRender({
|
|
1235
|
+
id: updatedByNameObj.id,
|
|
1236
|
+
barcode: updatedByNameObj.barcode,
|
|
1237
|
+
firstName: updatedByNameObj.firstName,
|
|
1238
|
+
lastName: updatedByNameObj.lastName
|
|
1239
|
+
})
|
|
1240
|
+
} else {
|
|
1241
|
+
setUpdatedByForRender({
|
|
1242
|
+
id: '',
|
|
1243
|
+
barcode: '',
|
|
1244
|
+
firstName: '',
|
|
1245
|
+
lastName: ''
|
|
1246
|
+
})
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
// is passed into Modal
|
|
1251
|
+
const handleIncidentTypeToggle = (type) => {
|
|
1252
|
+
// console.log("@EDIT - handleIncidentTypeToggle, type: ", type)
|
|
1253
|
+
setFormData((prevFormData) => {
|
|
1254
|
+
const currentTypes = new Set(prevFormData.incidentTypes.map(t => t.id));
|
|
1255
|
+
if(currentTypes.has(type.id)) {
|
|
1256
|
+
return {
|
|
1257
|
+
...prevFormData,
|
|
1258
|
+
incidentTypes: prevFormData.incidentTypes.filter(t => t.id !== type.id)
|
|
1259
|
+
};
|
|
1260
|
+
} else {
|
|
1261
|
+
return {
|
|
1262
|
+
...prevFormData,
|
|
1263
|
+
incidentTypes: [...prevFormData.incidentTypes, type]
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
})
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
const handleRemoveType = (typeId) => {
|
|
1270
|
+
setFormData((prevFormData) => ({
|
|
1271
|
+
...prevFormData,
|
|
1272
|
+
incidentTypes: prevFormData.incidentTypes.filter(type => type.id !== typeId)
|
|
1273
|
+
}));
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
const handleRemoveWitness = (witnessId) => {
|
|
1277
|
+
setFormData(prevFormData => ({
|
|
1278
|
+
...prevFormData,
|
|
1279
|
+
incidentWitnesses: prevFormData.incidentWitnesses.filter(wit => wit.id !== witnessId)
|
|
1280
|
+
}));
|
|
1281
|
+
setSelectedWitnesses(prevSelectedWitnesses => (
|
|
1282
|
+
prevSelectedWitnesses.filter(wit => wit.id !== witnessId)
|
|
1283
|
+
));
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
const handleMarkForRemoval = (mediaId) => {
|
|
1287
|
+
const updatedAttachments = formData.attachments.map((attachment) =>
|
|
1288
|
+
attachment.id === mediaId
|
|
1289
|
+
? { ...attachment, toBeRemoved: true }
|
|
1290
|
+
: attachment
|
|
1291
|
+
);
|
|
1292
|
+
setFormData((prevState) => ({
|
|
1293
|
+
...prevState,
|
|
1294
|
+
attachments: updatedAttachments,
|
|
1295
|
+
}));
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
const handleUndo = (mediaId) => {
|
|
1299
|
+
const updatedAttachments = formData.attachments.map((attachment) => {
|
|
1300
|
+
if(attachment.id === mediaId) {
|
|
1301
|
+
const { toBeRemoved, ...rest } = attachment;
|
|
1302
|
+
return { ...rest };
|
|
1303
|
+
}
|
|
1304
|
+
return attachment
|
|
1305
|
+
});
|
|
1306
|
+
setFormData((prevState) => ({
|
|
1307
|
+
...prevState,
|
|
1308
|
+
attachments: updatedAttachments,
|
|
1309
|
+
}));
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
const handleAddSelfAsWitness = () => {
|
|
1313
|
+
setFormData(prevFormData => {
|
|
1314
|
+
const isSelfAlreadyWitness = prevFormData.incidentWitnesses.some(
|
|
1315
|
+
wit => wit.id === self.id
|
|
1316
|
+
);
|
|
1317
|
+
if(isSelfAlreadyWitness) {
|
|
1318
|
+
return prevFormData
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
return {
|
|
1322
|
+
...prevFormData,
|
|
1323
|
+
incidentWitnesses: [
|
|
1324
|
+
...prevFormData.incidentWitnesses,
|
|
1325
|
+
self
|
|
1326
|
+
]
|
|
1327
|
+
};
|
|
1328
|
+
});
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
const handleOpenAddCustomWitness = () => {
|
|
1332
|
+
openModalCustomWitness()
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
const handleShowTrespassFormModal = (trespassCustomerId) => {
|
|
1336
|
+
setTrespassCustomerID(trespassCustomerId);
|
|
1337
|
+
openModalTrespass();
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
const handleShowCustomerDetailsFormModal = (custId) => {
|
|
1341
|
+
setDetailsCustomerID(custId);
|
|
1342
|
+
openModalCustomerDetails();
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
const handleShowCustomWitModalAsEdit = (witObj) => {
|
|
1346
|
+
setCustWitEditObj(witObj)
|
|
1347
|
+
openModalCustomWitness()
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
const handleShowCustomWitModalAsEditForUnsaved = (witId) => {
|
|
1351
|
+
setCustWitEditID(witId)
|
|
1352
|
+
openModalCustomWitness()
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1355
|
+
// handle rendering inc type 'title' via associated key of 'id'
|
|
1356
|
+
// instead of the instance's inc type 'title'
|
|
1357
|
+
const preparedIncidentTypes = useMemo(() => {
|
|
1358
|
+
return formData.incidentTypes.map(incidentType => {
|
|
1359
|
+
const foundType = incidentTypesList.find(type => type.id === incidentType.id);
|
|
1360
|
+
const notFoundId = incidentType.id;
|
|
1361
|
+
return {
|
|
1362
|
+
id: incidentType.id,
|
|
1363
|
+
title: foundType ? foundType.title :
|
|
1364
|
+
<FormattedMessage
|
|
1365
|
+
id="incident-type-not-found-fallback"
|
|
1366
|
+
values={{ id: notFoundId }}
|
|
1367
|
+
/>
|
|
1368
|
+
};
|
|
1369
|
+
});
|
|
1370
|
+
}, [formData.incidentTypes, incidentTypesList]);
|
|
1371
|
+
|
|
1372
|
+
const itemFormatterIncidentTypes = (item, index) => {
|
|
1373
|
+
if (!item) {
|
|
1374
|
+
// console.log("@itemFormatterIncidentTypes - no item ran ")
|
|
1375
|
+
return null;
|
|
1376
|
+
};
|
|
1377
|
+
return (
|
|
1378
|
+
<li key={item.id ?? index}>
|
|
1379
|
+
{item.title}
|
|
1380
|
+
<button
|
|
1381
|
+
style={{ paddingLeft: '8px' }}
|
|
1382
|
+
onClick={() => handleRemoveType(item.id)}
|
|
1383
|
+
type="button"
|
|
1384
|
+
aria-label={`Remove ${item.title}`}
|
|
1385
|
+
>
|
|
1386
|
+
<Icon icon="trash" size="medium" />
|
|
1387
|
+
</button>
|
|
1388
|
+
</li>
|
|
1389
|
+
);
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
const handleCustomerNa = (event) => {
|
|
1393
|
+
setFormData((prev) => ({
|
|
1394
|
+
...prev,
|
|
1395
|
+
customerNa: event.target.checked,
|
|
1396
|
+
}));
|
|
1397
|
+
setIsNoCustomer(prev => !prev );
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
const handleStaffSuppressed = (event) => {
|
|
1401
|
+
setFormData((prev) => ({
|
|
1402
|
+
...prev,
|
|
1403
|
+
staffSuppressed: event.target.checked,
|
|
1404
|
+
}));
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
const extractSnippet = (description) => {
|
|
1408
|
+
// regex remove HTML tags
|
|
1409
|
+
const text = description.replace(/<[^>]*>/g, '');
|
|
1410
|
+
return text.length > 60 ? text.slice(0, 57) + '...' : text;
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
// useEffect(() => {
|
|
1414
|
+
// console.log('[EditPane] allCustomers -> ', JSON.stringify(allCustomers, null, 2))
|
|
1415
|
+
// }, [allCustomers]);
|
|
1416
|
+
|
|
1417
|
+
const itemFormatterCustomers = (cust) => {
|
|
1418
|
+
const notAvailable = intl.formatMessage({ id: "unknown-name-placeholder" });
|
|
1419
|
+
|
|
1420
|
+
const firstName = cust.registered === false ?
|
|
1421
|
+
cust.firstName || notAvailable : cust.associatedFirstName;
|
|
1422
|
+
|
|
1423
|
+
const lastName = cust.registered === false ?
|
|
1424
|
+
cust.lastName || notAvailable : cust.associatedLastName;
|
|
1425
|
+
|
|
1426
|
+
const snippetOfDescription = cust.description ? extractSnippet(cust.description) : '';
|
|
1427
|
+
|
|
1428
|
+
const trespassServed = cust.trespass && cust.trespass.declarationOfService;
|
|
1429
|
+
|
|
1430
|
+
const name = `${lastName}, ${firstName}`;
|
|
1431
|
+
|
|
1432
|
+
return (
|
|
1433
|
+
<li key={cust.id} style={{ marginTop: '12px' }}>
|
|
1434
|
+
{cust.firstName === '' && cust.lastName === '' ? (
|
|
1435
|
+
snippetOfDescription
|
|
1436
|
+
) : cust.registered ? (
|
|
1437
|
+
<a
|
|
1438
|
+
href={`/users/preview/${cust.id}`}
|
|
1439
|
+
target="_blank"
|
|
1440
|
+
aria-label="Link to customer in users application"
|
|
1441
|
+
style={{
|
|
1442
|
+
textDecoration: 'none',
|
|
1443
|
+
color: 'rgb(0,0,238)',
|
|
1444
|
+
fontWeight: 'bold',
|
|
1445
|
+
}}
|
|
1446
|
+
rel="noreferrer"
|
|
1447
|
+
>
|
|
1448
|
+
{/* render associated key name if instance customer, else if newly added customer show name */}
|
|
1449
|
+
{cust.associatedFirstName ? name : `${cust.lastName}, ${cust.firstName}`}
|
|
1450
|
+
</a>
|
|
1451
|
+
) : (
|
|
1452
|
+
name // unregistered customer show instance name
|
|
1453
|
+
)}
|
|
1454
|
+
|
|
1455
|
+
{/* customer has been served their trespass */}
|
|
1456
|
+
{trespassServed ? (
|
|
1457
|
+
<span style={{ marginLeft: '10px', color: 'green' }}>
|
|
1458
|
+
<Icon icon="check-circle" />
|
|
1459
|
+
</span>
|
|
1460
|
+
) : null}
|
|
1461
|
+
|
|
1462
|
+
<button
|
|
1463
|
+
style={{ paddingLeft: '8px' }}
|
|
1464
|
+
onClick={() => handleRemoveCustomer(cust.id)}
|
|
1465
|
+
type="button"
|
|
1466
|
+
aria-label={`Remove ${name} as customer`}
|
|
1467
|
+
>
|
|
1468
|
+
<Icon icon="trash" size="medium" />
|
|
1469
|
+
</button>
|
|
1470
|
+
|
|
1471
|
+
{cust.details || cust.description ? (
|
|
1472
|
+
<button
|
|
1473
|
+
style={{ paddingLeft: '15px' }}
|
|
1474
|
+
onClick={() => handleShowCustomerDetailsFormModal(cust.id)}
|
|
1475
|
+
type="button"
|
|
1476
|
+
>
|
|
1477
|
+
<Icon icon="report" size="medium" />
|
|
1478
|
+
<FormattedMessage id="edit-details-button"/>
|
|
1479
|
+
</button>
|
|
1480
|
+
) : (
|
|
1481
|
+
<button
|
|
1482
|
+
style={{ paddingLeft: '15px' }}
|
|
1483
|
+
onClick={() => handleShowCustomerDetailsFormModal(cust.id)}
|
|
1484
|
+
type="button"
|
|
1485
|
+
>
|
|
1486
|
+
<Icon icon="plus-sign" size="medium" />
|
|
1487
|
+
<FormattedMessage id="add-details-button"/>
|
|
1488
|
+
</button>
|
|
1489
|
+
)}
|
|
1490
|
+
|
|
1491
|
+
{cust.trespass ? (
|
|
1492
|
+
<button
|
|
1493
|
+
style={{ paddingLeft: '15px' }}
|
|
1494
|
+
onClick={() => handleShowTrespassFormModal(cust.id)}
|
|
1495
|
+
type="button"
|
|
1496
|
+
>
|
|
1497
|
+
<Icon icon="report" size="medium" />
|
|
1498
|
+
<FormattedMessage id="edit-trespass-button"/>
|
|
1499
|
+
</button>
|
|
1500
|
+
) : (
|
|
1501
|
+
<button
|
|
1502
|
+
style={{ paddingLeft: '15px' }}
|
|
1503
|
+
onClick={() => handleShowTrespassFormModal(cust.id)}
|
|
1504
|
+
type="button"
|
|
1505
|
+
>
|
|
1506
|
+
<Icon icon="plus-sign" size="medium" />
|
|
1507
|
+
<FormattedMessage id="add-trespass-button"/>
|
|
1508
|
+
</button>
|
|
1509
|
+
)}
|
|
1510
|
+
</li>
|
|
1511
|
+
);
|
|
1512
|
+
};
|
|
1513
|
+
|
|
1514
|
+
const witnessItemFormatter = (wit) => {
|
|
1515
|
+
const isCustomNotSaved = formData.incidentWitnesses.filter(incWit => incWit.id === wit.id).length === 0;
|
|
1516
|
+
return (
|
|
1517
|
+
<li key={wit.id}>
|
|
1518
|
+
{wit.isCustom === true ? (
|
|
1519
|
+
// custom witness
|
|
1520
|
+
<>
|
|
1521
|
+
{wit.lastName}, {wit.firstName}
|
|
1522
|
+
{isCustomNotSaved ? (
|
|
1523
|
+
// show handler for edit unsaved custom witness
|
|
1524
|
+
<button
|
|
1525
|
+
style={{ paddingLeft: '15px' }}
|
|
1526
|
+
onClick={() => handleShowCustomWitModalAsEditForUnsaved(wit.id)}
|
|
1527
|
+
type="button"
|
|
1528
|
+
>
|
|
1529
|
+
<Icon icon="report" size="medium" /> Edit
|
|
1530
|
+
</button>)
|
|
1531
|
+
: (
|
|
1532
|
+
<button
|
|
1533
|
+
style={{ paddingLeft: '15px' }}
|
|
1534
|
+
onClick={() => handleShowCustomWitModalAsEdit(wit)}
|
|
1535
|
+
type="button"
|
|
1536
|
+
>
|
|
1537
|
+
<Icon icon="report" size="medium" /> Edit
|
|
1538
|
+
</button>
|
|
1539
|
+
)}
|
|
1540
|
+
</>
|
|
1541
|
+
) : wit.associatedFirstName ? (
|
|
1542
|
+
// Users witness
|
|
1543
|
+
<a
|
|
1544
|
+
href={`/users/preview/${wit.id}`}
|
|
1545
|
+
target="_blank"
|
|
1546
|
+
aria-label="Link to customer in users application"
|
|
1547
|
+
style={{
|
|
1548
|
+
textDecoration: 'none',
|
|
1549
|
+
color: 'rgb(0,0,238)',
|
|
1550
|
+
fontWeight: 'bold',
|
|
1551
|
+
}}
|
|
1552
|
+
rel="noreferrer"
|
|
1553
|
+
>
|
|
1554
|
+
{`${wit.associatedLastName}, ${wit.associatedFirstName}`}
|
|
1555
|
+
</a>
|
|
1556
|
+
) : wit.id ? (
|
|
1557
|
+
// newly selected Users witness (not yet saved)
|
|
1558
|
+
<a
|
|
1559
|
+
href={`/users/preview/${wit.id}`}
|
|
1560
|
+
target="_blank"
|
|
1561
|
+
aria-label="Link to customer in users application"
|
|
1562
|
+
style={{
|
|
1563
|
+
textDecoration: 'none',
|
|
1564
|
+
color: 'rgb(0,0,238)',
|
|
1565
|
+
fontWeight: 'bold',
|
|
1566
|
+
}}
|
|
1567
|
+
rel="noreferrer"
|
|
1568
|
+
>
|
|
1569
|
+
{`${wit.lastName}, ${wit.firstName}`}
|
|
1570
|
+
</a>) : null}
|
|
1571
|
+
|
|
1572
|
+
<button
|
|
1573
|
+
style={{ paddingLeft: '8px' }}
|
|
1574
|
+
onClick={() => handleRemoveWitness(wit.id)}
|
|
1575
|
+
type="button"
|
|
1576
|
+
aria-label={`Remove ${wit.lastName}, ${wit.firstName} as witness`}
|
|
1577
|
+
>
|
|
1578
|
+
<Icon icon="trash" size="medium" />
|
|
1579
|
+
</button>
|
|
1580
|
+
</li>
|
|
1581
|
+
);
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
const handleOpenModalAddMedia = () => {
|
|
1585
|
+
openModalMedia();
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
const customersListLabel = intl.formatMessage(
|
|
1589
|
+
{ id: `customers-list-label` },
|
|
1590
|
+
{ count: formData.customers.length }
|
|
1591
|
+
);
|
|
1592
|
+
|
|
1593
|
+
const incidentTypesListLabel = intl.formatMessage(
|
|
1594
|
+
{ id: `incident-types-list-label` },
|
|
1595
|
+
{ count: preparedIncidentTypes.length }
|
|
1596
|
+
);
|
|
1597
|
+
|
|
1598
|
+
const witnessesListLabel = intl.formatMessage(
|
|
1599
|
+
{ id: `witnesses-list-label` },
|
|
1600
|
+
{ count: allWitnesses.length,
|
|
1601
|
+
bold: (chunks) => (
|
|
1602
|
+
<strong style={{ color: '#A12A2A' }}>{chunks}</strong>
|
|
1603
|
+
)
|
|
1604
|
+
}
|
|
1605
|
+
);
|
|
1606
|
+
|
|
1607
|
+
const editorModules = {
|
|
1608
|
+
toolbar: [
|
|
1609
|
+
[{ 'header': [1, 2, false] }],
|
|
1610
|
+
['bold', 'italic', 'underline'],
|
|
1611
|
+
],
|
|
1612
|
+
};
|
|
1613
|
+
|
|
1614
|
+
const canSave = isHydrated && isFormDataValid() && (formIsDirty ||staffSuppressedIsDirty);
|
|
1615
|
+
|
|
1616
|
+
const footer = (
|
|
1617
|
+
<PaneFooter
|
|
1618
|
+
renderStart={
|
|
1619
|
+
<Button onClick={handleClickDismissCancel}>
|
|
1620
|
+
<FormattedMessage id="cancel-button" />
|
|
1621
|
+
</Button>
|
|
1622
|
+
}
|
|
1623
|
+
renderEnd={
|
|
1624
|
+
<Button
|
|
1625
|
+
buttonStyle="primary"
|
|
1626
|
+
onClick={handleSaveAndCloseClick}
|
|
1627
|
+
disabled={!canSave}
|
|
1628
|
+
>
|
|
1629
|
+
<FormattedMessage id="save-and-close-button" />
|
|
1630
|
+
</Button>
|
|
1631
|
+
}
|
|
1632
|
+
/>
|
|
1633
|
+
);
|
|
1634
|
+
|
|
1635
|
+
return (
|
|
1636
|
+
<>
|
|
1637
|
+
{id && <GetDetails id={id} />}
|
|
1638
|
+
{isLoadingDetails ? (
|
|
1639
|
+
<LoadingPane
|
|
1640
|
+
defaultWidth="fill"
|
|
1641
|
+
paneTitle={<FormattedMessage id="edit-pane.loading-pane-paneTitle" />}
|
|
1642
|
+
/>
|
|
1643
|
+
) : (
|
|
1644
|
+
<>
|
|
1645
|
+
{id && putData && (
|
|
1646
|
+
<UpdateReport
|
|
1647
|
+
id={id}
|
|
1648
|
+
data={putData}
|
|
1649
|
+
handleCloseEdit={handleCloseEdit}
|
|
1650
|
+
/>
|
|
1651
|
+
)}
|
|
1652
|
+
|
|
1653
|
+
{idForMediaCreate && formDataArrayForMediaCreate && (
|
|
1654
|
+
<CreateMedia
|
|
1655
|
+
id={idForMediaCreate}
|
|
1656
|
+
formDataArray={formDataArrayForMediaCreate}
|
|
1657
|
+
handleCloseEdit={handleCloseEdit}
|
|
1658
|
+
context="edit"
|
|
1659
|
+
/>
|
|
1660
|
+
)}
|
|
1661
|
+
|
|
1662
|
+
{isUpdatingReport ? (
|
|
1663
|
+
<LoadingPane
|
|
1664
|
+
defaultWidth="fill"
|
|
1665
|
+
paneTitle={<FormattedMessage id="edit-pane.loading-pane-submit-paneTitle" />}
|
|
1666
|
+
/>
|
|
1667
|
+
) : (
|
|
1668
|
+
<Pane
|
|
1669
|
+
paneTitle={<FormattedMessage id="edit-pane.paneTitle" />}
|
|
1670
|
+
defaultWidth="fill"
|
|
1671
|
+
renderHeader={(renderProps) => (
|
|
1672
|
+
<PaneHeader
|
|
1673
|
+
{...renderProps}
|
|
1674
|
+
dismissible
|
|
1675
|
+
onClose={handleClickDismissCancel}
|
|
1676
|
+
/>
|
|
1677
|
+
)}
|
|
1678
|
+
footer={footer}
|
|
1679
|
+
>
|
|
1680
|
+
<GetTrespassTemplates />
|
|
1681
|
+
<GetTrespassReasons />
|
|
1682
|
+
|
|
1683
|
+
{showDirtyFormModal && (
|
|
1684
|
+
<ModalDirtyFormWarn
|
|
1685
|
+
handleKeepEditing={handleKeepEditing}
|
|
1686
|
+
handleDismissOnDirty={handleDismissOnDirty}
|
|
1687
|
+
/>
|
|
1688
|
+
)}
|
|
1689
|
+
|
|
1690
|
+
<GetSummary
|
|
1691
|
+
ids={idsArray}
|
|
1692
|
+
onResult={setLinkedToSummaries}
|
|
1693
|
+
/>
|
|
1694
|
+
|
|
1695
|
+
{showModalLinkIncident && (
|
|
1696
|
+
<ModalLinkIncident
|
|
1697
|
+
toggleRowChecked={toggleRowChecked}
|
|
1698
|
+
ids={allLinkedTo}
|
|
1699
|
+
setIds={setAllLinkedTo}
|
|
1700
|
+
handleCloseModalLinkIncident={handleCloseModalLinkIncident}
|
|
1701
|
+
/>
|
|
1702
|
+
)}
|
|
1703
|
+
|
|
1704
|
+
{isModalAttentionDecOfService && (
|
|
1705
|
+
<ModalAttentionDecOfService
|
|
1706
|
+
onNo={handleClickNo}
|
|
1707
|
+
onYes={handlClickYes}
|
|
1708
|
+
missingIds={missingIds}
|
|
1709
|
+
allCustomers={allCustomers}
|
|
1710
|
+
/>
|
|
1711
|
+
)}
|
|
1712
|
+
|
|
1713
|
+
{trespassCustomerID && (
|
|
1714
|
+
<ModalTrespass
|
|
1715
|
+
customerID={trespassCustomerID}
|
|
1716
|
+
setAllCustomers={setAllCustomers}
|
|
1717
|
+
allCustomers={allCustomers}
|
|
1718
|
+
onStagedTrespassUpdate={handleStagedTrespassUpdate}
|
|
1719
|
+
updateDeclarationArray={customersToUpdateDeclaration}
|
|
1720
|
+
onUpdateDeclaration={handleUpdateDeclaration}
|
|
1721
|
+
customersWithoutDeclaration={customersWithoutDeclaration}
|
|
1722
|
+
isNewlySelected={newlyAddedCustomerIds.has(trespassCustomerID)}
|
|
1723
|
+
/>
|
|
1724
|
+
)}
|
|
1725
|
+
|
|
1726
|
+
{detailsCustomerID && (
|
|
1727
|
+
<ModalCustomerDetails
|
|
1728
|
+
customerID={detailsCustomerID}
|
|
1729
|
+
setAllCustomers={setAllCustomers}
|
|
1730
|
+
allCustomers={allCustomers}
|
|
1731
|
+
/>
|
|
1732
|
+
)}
|
|
1733
|
+
{custWitEditObj && Object.keys(custWitEditObj).length > 0 ? (
|
|
1734
|
+
// Edit saved custom witness
|
|
1735
|
+
<ModalCustomWitness
|
|
1736
|
+
context='editSavedCustomWitness'
|
|
1737
|
+
formData={formData}
|
|
1738
|
+
setFormData={setFormData}
|
|
1739
|
+
setCustWitEditObj={setCustWitEditObj}
|
|
1740
|
+
custWitEditObj={custWitEditObj}
|
|
1741
|
+
/>
|
|
1742
|
+
) : custWitEditID ? (
|
|
1743
|
+
// Edit un-saved custom witness
|
|
1744
|
+
<ModalCustomWitness
|
|
1745
|
+
// utilizes selectedWitnesses React context at Modal
|
|
1746
|
+
custWitEditID={custWitEditID}
|
|
1747
|
+
/>
|
|
1748
|
+
) : (
|
|
1749
|
+
// Add a new custom witness
|
|
1750
|
+
<ModalCustomWitness
|
|
1751
|
+
// utilizes selectedWitnesses React context at Modal
|
|
1752
|
+
context='addCustomWitAtEdit'
|
|
1753
|
+
/>
|
|
1754
|
+
)}
|
|
1755
|
+
|
|
1756
|
+
{singleIncident.customers?.map((cust) => {
|
|
1757
|
+
if (cust.registered === false) {
|
|
1758
|
+
return null
|
|
1759
|
+
}
|
|
1760
|
+
return (
|
|
1761
|
+
<GetName
|
|
1762
|
+
key={cust.id}
|
|
1763
|
+
uuid={cust.id}
|
|
1764
|
+
handleGetCustName={handleGetCustName}
|
|
1765
|
+
handleMissingUsers={handleMissingUsers}
|
|
1766
|
+
context='customer'
|
|
1767
|
+
/>
|
|
1768
|
+
)
|
|
1769
|
+
})}
|
|
1770
|
+
|
|
1771
|
+
{formData.incidentWitnesses?.map((wit) => (
|
|
1772
|
+
<GetName
|
|
1773
|
+
isCustomWitness={wit.isCustom === true ? wit : null}
|
|
1774
|
+
key={wit.id}
|
|
1775
|
+
uuid={wit.id}
|
|
1776
|
+
handleGetWitnessName={handleGetWitnessName}
|
|
1777
|
+
handleMissingUsers={handleMissingUsers}
|
|
1778
|
+
context='witness'/>
|
|
1779
|
+
))
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
{updatedById && updatedById !== '' && (
|
|
1783
|
+
<GetName
|
|
1784
|
+
uuid={updatedById}
|
|
1785
|
+
handleGetUpdatedByName={handleGetUpdatedByName}
|
|
1786
|
+
handleMissingUsers={handleMissingUsers}
|
|
1787
|
+
context="updatedBy"
|
|
1788
|
+
/>
|
|
1789
|
+
)}
|
|
1790
|
+
|
|
1791
|
+
{createdById && createdById !== '' && (
|
|
1792
|
+
<GetNameCreatedBy
|
|
1793
|
+
uuid={createdById}
|
|
1794
|
+
handleGetCreatedByName={handleGetCreatedByName}
|
|
1795
|
+
handleMissingUsers={handleMissingUsers}
|
|
1796
|
+
/>
|
|
1797
|
+
)}
|
|
1798
|
+
|
|
1799
|
+
<GetLocationsInService />
|
|
1800
|
+
<GetIncidentTypesDetails context='incidents'/>
|
|
1801
|
+
<ModalAddMedia
|
|
1802
|
+
context='edit'
|
|
1803
|
+
handleAddMedia={handleAddMedia}
|
|
1804
|
+
/>
|
|
1805
|
+
<ModalDescribeCustomer />
|
|
1806
|
+
<ModalSelectKnownCustomer
|
|
1807
|
+
context='edit'
|
|
1808
|
+
setRemovedCustomerIds={setRemovedCustomerIds}
|
|
1809
|
+
removedCustomerIds={removedCustomerIds}
|
|
1810
|
+
setFormData={setFormData}
|
|
1811
|
+
formData={formData}
|
|
1812
|
+
/>
|
|
1813
|
+
<ModalSelectWitness
|
|
1814
|
+
context='edit'
|
|
1815
|
+
setFormData={setFormData}
|
|
1816
|
+
formData={formData}
|
|
1817
|
+
setRemovedWitnessIds={setRemovedWitnessIds}
|
|
1818
|
+
removedWitnessIds={removedWitnessIds}
|
|
1819
|
+
/>
|
|
1820
|
+
<ModalSelectIncidentTypes
|
|
1821
|
+
handleIncidentTypeToggle={handleIncidentTypeToggle}
|
|
1822
|
+
formDataIncidentTypes={formData.incidentTypes}
|
|
1823
|
+
/>
|
|
1824
|
+
|
|
1825
|
+
<GetSelf />
|
|
1826
|
+
|
|
1827
|
+
{/* if 404 for any /Users request get associated key for name, render MessageBanner with message and those unfound uuid(s) */}
|
|
1828
|
+
<Row>
|
|
1829
|
+
<Col xs={12}>
|
|
1830
|
+
<div>
|
|
1831
|
+
<MessageBanner
|
|
1832
|
+
dismissible
|
|
1833
|
+
type="error"
|
|
1834
|
+
show={missingUsers.length > 0}
|
|
1835
|
+
>
|
|
1836
|
+
{<FormattedMessage
|
|
1837
|
+
id="message-banner.error-missing-users-404"
|
|
1838
|
+
values={{ ids: missingUsers.join(', ') }}
|
|
1839
|
+
/>}
|
|
1840
|
+
</MessageBanner>
|
|
1841
|
+
</div>
|
|
1842
|
+
</Col>
|
|
1843
|
+
</Row>
|
|
1844
|
+
|
|
1845
|
+
<AccordionSet>
|
|
1846
|
+
<ExpandAllButton />
|
|
1847
|
+
<Accordion
|
|
1848
|
+
closedByDefault={true}
|
|
1849
|
+
label={<FormattedMessage
|
|
1850
|
+
id="edit-pane.accordion-administrative-data-label"/>}>
|
|
1851
|
+
<MetaSection
|
|
1852
|
+
headingLevel={4}
|
|
1853
|
+
useAccordion
|
|
1854
|
+
showUserLink
|
|
1855
|
+
createdDate={formData?.metadata?.createdDate || null}
|
|
1856
|
+
lastUpdatedDate={formData?.metadata?.updatedDate || null}
|
|
1857
|
+
createdBy={{
|
|
1858
|
+
id: createdByForRender.id,
|
|
1859
|
+
personal: {
|
|
1860
|
+
firstName: createdByForRender.firstName,
|
|
1861
|
+
lastName: createdByForRender.lastName
|
|
1862
|
+
}
|
|
1863
|
+
}}
|
|
1864
|
+
lastUpdatedBy={{
|
|
1865
|
+
id: updatedByForRender.id,
|
|
1866
|
+
personal: {
|
|
1867
|
+
firstName: updatedByForRender.firstName,
|
|
1868
|
+
lastName: updatedByForRender.lastName,
|
|
1869
|
+
}
|
|
1870
|
+
}}
|
|
1871
|
+
/>
|
|
1872
|
+
<Row>
|
|
1873
|
+
<Col xs={6}>
|
|
1874
|
+
<Checkbox
|
|
1875
|
+
label={<FormattedMessage id="edit-pane.checkbox-staff-suppress"/>}
|
|
1876
|
+
checked={formData.staffSuppressed}
|
|
1877
|
+
name='staffSuppressed'
|
|
1878
|
+
onChange={handleStaffSuppressed}
|
|
1879
|
+
/>
|
|
1880
|
+
</Col>
|
|
1881
|
+
</Row>
|
|
1882
|
+
</Accordion>
|
|
1883
|
+
<Accordion
|
|
1884
|
+
label={<FormattedMessage id="accordion-label-customers" />}
|
|
1885
|
+
>
|
|
1886
|
+
<Row>
|
|
1887
|
+
<Col xs={2} style={{ marginTop: '10px', marginLeft: '10px', marginBottom: '10px'}}>
|
|
1888
|
+
<Checkbox
|
|
1889
|
+
label={<FormattedMessage id="customer-not-available"/>}
|
|
1890
|
+
checked={formData.customerNa}
|
|
1891
|
+
name='customerNa'
|
|
1892
|
+
onChange={handleCustomerNa}
|
|
1893
|
+
/>
|
|
1894
|
+
</Col>
|
|
1895
|
+
</Row>
|
|
1896
|
+
{!isNoCustomer && (
|
|
1897
|
+
<>
|
|
1898
|
+
<Row>
|
|
1899
|
+
<Col xs={3}>
|
|
1900
|
+
<Button
|
|
1901
|
+
style={{ marginTop: '25px' }}
|
|
1902
|
+
onClick={openModalSelectKnownCust}
|
|
1903
|
+
>
|
|
1904
|
+
<FormattedMessage id="select-add-known-customer-button" />
|
|
1905
|
+
</Button>
|
|
1906
|
+
</Col>
|
|
1907
|
+
</Row>
|
|
1908
|
+
<Row>
|
|
1909
|
+
<Col xs={3}>
|
|
1910
|
+
<Button
|
|
1911
|
+
style={{ marginTop: '25px' }}
|
|
1912
|
+
onClick={openModalUnknownCust}
|
|
1913
|
+
>
|
|
1914
|
+
<FormattedMessage id="describe-add-unknown-customer-button" />
|
|
1915
|
+
</Button>
|
|
1916
|
+
</Col>
|
|
1917
|
+
</Row>
|
|
1918
|
+
<Row>
|
|
1919
|
+
<Col xs={6}>
|
|
1920
|
+
<Label style={{ marginTop: '5px' }} size="medium" tag="h2" id='customer-list-label'>
|
|
1921
|
+
<b>{customersListLabel}</b>
|
|
1922
|
+
</Label>
|
|
1923
|
+
</Col>
|
|
1924
|
+
</Row>
|
|
1925
|
+
<Row>
|
|
1926
|
+
<Col xs={12}>
|
|
1927
|
+
<List
|
|
1928
|
+
aria-labelledby='customer-list-label'
|
|
1929
|
+
listStyle="bullets"
|
|
1930
|
+
label="Customers"
|
|
1931
|
+
items={allCustomers}
|
|
1932
|
+
isEmptyMessage={
|
|
1933
|
+
<FormattedMessage
|
|
1934
|
+
id="customers-list-is-empty-message"
|
|
1935
|
+
values={{
|
|
1936
|
+
bold: (chunks) => (
|
|
1937
|
+
<strong style={{ color: '#A12A2A' }}>
|
|
1938
|
+
{chunks}
|
|
1939
|
+
</strong>
|
|
1940
|
+
)
|
|
1941
|
+
}}
|
|
1942
|
+
/>
|
|
1943
|
+
}
|
|
1944
|
+
itemFormatter={itemFormatterCustomers}
|
|
1945
|
+
/>
|
|
1946
|
+
</Col>
|
|
1947
|
+
</Row>
|
|
1948
|
+
</>
|
|
1949
|
+
|
|
1950
|
+
)}
|
|
1951
|
+
</Accordion>
|
|
1952
|
+
|
|
1953
|
+
<Accordion
|
|
1954
|
+
label={<FormattedMessage id="accordion-label-incident" />}
|
|
1955
|
+
>
|
|
1956
|
+
<Row>
|
|
1957
|
+
<Col xs={3}>
|
|
1958
|
+
<Select
|
|
1959
|
+
required
|
|
1960
|
+
label={
|
|
1961
|
+
<FormattedMessage id="edit-pane.incident-location-select-label" />
|
|
1962
|
+
}
|
|
1963
|
+
name="incidentLocation"
|
|
1964
|
+
value={formData.incidentLocation}
|
|
1965
|
+
dataOptions={locationDataOptions}
|
|
1966
|
+
onChange={handleChange}
|
|
1967
|
+
/>
|
|
1968
|
+
</Col>
|
|
1969
|
+
|
|
1970
|
+
<Col xs={3}>
|
|
1971
|
+
<Select
|
|
1972
|
+
label={
|
|
1973
|
+
<FormattedMessage id="edit-pane.sub-location-select-label" />
|
|
1974
|
+
}
|
|
1975
|
+
name="subLocation"
|
|
1976
|
+
value={formData.subLocation}
|
|
1977
|
+
dataOptions={subLocationsDataOptions}
|
|
1978
|
+
onChange={handleChange}
|
|
1979
|
+
/>
|
|
1980
|
+
</Col>
|
|
1981
|
+
</Row>
|
|
1982
|
+
|
|
1983
|
+
<Row>
|
|
1984
|
+
<Col xs={3}>
|
|
1985
|
+
<Datepicker
|
|
1986
|
+
required
|
|
1987
|
+
name="dateTimeOfIncident"
|
|
1988
|
+
value={formData.dateTimeOfIncident}
|
|
1989
|
+
label={
|
|
1990
|
+
<FormattedMessage id="edit-pane.date-of-incident-date-picker-label" />
|
|
1991
|
+
}
|
|
1992
|
+
onChange={handleChange}
|
|
1993
|
+
/>
|
|
1994
|
+
</Col>
|
|
1995
|
+
|
|
1996
|
+
<Col xs={2}>
|
|
1997
|
+
<Timepicker
|
|
1998
|
+
required
|
|
1999
|
+
name="timeOfIncident"
|
|
2000
|
+
value={formData.timeOfIncident}
|
|
2001
|
+
label={
|
|
2002
|
+
<FormattedMessage id="edit-pane.time-of-incident-date-picker-label" />
|
|
2003
|
+
}
|
|
2004
|
+
onChange={handleChange}
|
|
2005
|
+
/>
|
|
2006
|
+
</Col>
|
|
2007
|
+
|
|
2008
|
+
<Col xs={2} style={{ marginTop: '25px' }}>
|
|
2009
|
+
<Checkbox
|
|
2010
|
+
label='Approximate time'
|
|
2011
|
+
name='isApproximateTime'
|
|
2012
|
+
checked={formData.isApproximateTime}
|
|
2013
|
+
onChange={handleChange}
|
|
2014
|
+
/>
|
|
2015
|
+
</Col>
|
|
2016
|
+
</Row>
|
|
2017
|
+
|
|
2018
|
+
<Row>
|
|
2019
|
+
<Col xs={4}>
|
|
2020
|
+
<Button
|
|
2021
|
+
onClick={openModalSelectTypes}
|
|
2022
|
+
style={{ marginTop: '15px' }}
|
|
2023
|
+
>
|
|
2024
|
+
<FormattedMessage id="edit-pane.select-add-incident-type-button" />
|
|
2025
|
+
</Button>
|
|
2026
|
+
</Col>
|
|
2027
|
+
</Row>
|
|
2028
|
+
|
|
2029
|
+
<Row>
|
|
2030
|
+
<Col xs={8} style={{ paddingLeft: '20px' }}>
|
|
2031
|
+
<Label style={{ marginTop: '5px' }} size="medium" tag="h2">
|
|
2032
|
+
<b>{incidentTypesListLabel}</b>
|
|
2033
|
+
</Label>
|
|
2034
|
+
<List
|
|
2035
|
+
listStyle="bullets"
|
|
2036
|
+
label={incidentTypesListLabel}
|
|
2037
|
+
items={preparedIncidentTypes}
|
|
2038
|
+
isEmptyMessage={
|
|
2039
|
+
<FormattedMessage
|
|
2040
|
+
id="incident-types-list-is-empty-message"
|
|
2041
|
+
values={{
|
|
2042
|
+
bold: (chunks) => (
|
|
2043
|
+
<strong style={{ color: '#A12A2A' }}>
|
|
2044
|
+
{chunks}
|
|
2045
|
+
</strong>
|
|
2046
|
+
),
|
|
2047
|
+
}}
|
|
2048
|
+
/>
|
|
2049
|
+
}
|
|
2050
|
+
itemFormatter={itemFormatterIncidentTypes}
|
|
2051
|
+
/>
|
|
2052
|
+
</Col>
|
|
2053
|
+
</Row>
|
|
2054
|
+
|
|
2055
|
+
<Row style={{ marginTop: '25px' }}>
|
|
2056
|
+
<Col xs={6}>
|
|
2057
|
+
<Editor
|
|
2058
|
+
required
|
|
2059
|
+
label={
|
|
2060
|
+
<FormattedMessage id="edit-pane.incident-description" />
|
|
2061
|
+
}
|
|
2062
|
+
defaultValue={formData.detailedDescriptionOfIncident}
|
|
2063
|
+
onChange={handleDescriptionChange}
|
|
2064
|
+
onBlur={handleEditorBlur}
|
|
2065
|
+
onKeyDown={handleEditorKeyDown}
|
|
2066
|
+
modules={editorModules}
|
|
2067
|
+
/>
|
|
2068
|
+
</Col>
|
|
2069
|
+
</Row>
|
|
2070
|
+
|
|
2071
|
+
<Row>
|
|
2072
|
+
<Col xs={2} style={{ paddingTop: '25px' }}>
|
|
2073
|
+
<Button onClick={openModalSelectWitness}>
|
|
2074
|
+
<FormattedMessage id="select-add-witness-button" />
|
|
2075
|
+
</Button>
|
|
2076
|
+
</Col>
|
|
2077
|
+
</Row>
|
|
2078
|
+
|
|
2079
|
+
<Row>
|
|
2080
|
+
<Col xs={2} style={{ paddingTop: '25px' }}>
|
|
2081
|
+
<Button onClick={handleAddSelfAsWitness}>
|
|
2082
|
+
<FormattedMessage id="add-self-witness-button" />
|
|
2083
|
+
</Button>
|
|
2084
|
+
</Col>
|
|
2085
|
+
</Row>
|
|
2086
|
+
|
|
2087
|
+
<Row>
|
|
2088
|
+
<Col xs={2} style={{ paddingTop: '25px' }}>
|
|
2089
|
+
<Button
|
|
2090
|
+
onClick={handleOpenAddCustomWitness}
|
|
2091
|
+
>
|
|
2092
|
+
<FormattedMessage id="add-custom-witness-button" />
|
|
2093
|
+
</Button>
|
|
2094
|
+
</Col>
|
|
2095
|
+
</Row>
|
|
2096
|
+
|
|
2097
|
+
<Row>
|
|
2098
|
+
<Col xs={2} style={{ paddingTop: '25px' }}>
|
|
2099
|
+
<Button onClick={handleOpenModalLinkIncident}>
|
|
2100
|
+
<FormattedMessage id="link-to-button" />
|
|
2101
|
+
</Button>
|
|
2102
|
+
</Col>
|
|
2103
|
+
</Row>
|
|
2104
|
+
|
|
2105
|
+
<Row>
|
|
2106
|
+
<Col xs={8}>
|
|
2107
|
+
<Label style={{ marginTop: '5px' }} size="medium" tag="h2">
|
|
2108
|
+
<FormattedMessage
|
|
2109
|
+
id="witnesses-list-label"
|
|
2110
|
+
defaultMessage="{count, plural, one {{bold}Witness} other {{bold}Witnesses}}"
|
|
2111
|
+
values={{
|
|
2112
|
+
count: allWitnesses.length,
|
|
2113
|
+
bold: (chunks) => <strong>{chunks}</strong>,
|
|
2114
|
+
}}
|
|
2115
|
+
/>
|
|
2116
|
+
</Label>
|
|
2117
|
+
<List
|
|
2118
|
+
listStyle="bullets"
|
|
2119
|
+
label={witnessesListLabel}
|
|
2120
|
+
items={allWitnesses}
|
|
2121
|
+
isEmptyMessage={
|
|
2122
|
+
<FormattedMessage
|
|
2123
|
+
id="witnesses-list-is-empty-message"
|
|
2124
|
+
values={{
|
|
2125
|
+
bold: (chunks) => (
|
|
2126
|
+
<strong style={{ color: '#A12A2A' }}>
|
|
2127
|
+
{chunks}
|
|
2128
|
+
</strong>
|
|
2129
|
+
),
|
|
2130
|
+
}}
|
|
2131
|
+
/>
|
|
2132
|
+
}
|
|
2133
|
+
itemFormatter={witnessItemFormatter}
|
|
2134
|
+
/>
|
|
2135
|
+
</Col>
|
|
2136
|
+
</Row>
|
|
2137
|
+
|
|
2138
|
+
{linkedToSummaries.length > 0 ? (
|
|
2139
|
+
<Row style={{ marginTop: '25px' }}>
|
|
2140
|
+
<Col xs={2}>
|
|
2141
|
+
<KeyValue
|
|
2142
|
+
label={<FormattedMessage id="linked-incidents-label"/>}
|
|
2143
|
+
value={
|
|
2144
|
+
<div style={{ display: 'grid', rowGap: '10px' }}>
|
|
2145
|
+
{linkedToSummaries.map((ltS) => (
|
|
2146
|
+
<LinkedIncident
|
|
2147
|
+
key={ltS.id}
|
|
2148
|
+
summaryObj={ltS}
|
|
2149
|
+
onDelete={handleTrashLinkedIncident}
|
|
2150
|
+
renderContext='create-edit'
|
|
2151
|
+
/>
|
|
2152
|
+
))}
|
|
2153
|
+
</div>
|
|
2154
|
+
}
|
|
2155
|
+
/>
|
|
2156
|
+
</Col>
|
|
2157
|
+
</Row>
|
|
2158
|
+
) : null}
|
|
2159
|
+
</Accordion>
|
|
2160
|
+
|
|
2161
|
+
<Accordion label={<FormattedMessage id="accordion-label-media" />}>
|
|
2162
|
+
<div>
|
|
2163
|
+
{mediaArray.map((attachment) => (
|
|
2164
|
+
<GetMedia
|
|
2165
|
+
context='thumbnail'
|
|
2166
|
+
contentType={attachment.contentType}
|
|
2167
|
+
key={attachment.id}
|
|
2168
|
+
id={id}
|
|
2169
|
+
imageId={attachment.id}
|
|
2170
|
+
mediaHandler={(mediaUrl) => handleMediaUrl(mediaUrl, attachment.id)}
|
|
2171
|
+
/>
|
|
2172
|
+
))}
|
|
2173
|
+
</div>
|
|
2174
|
+
|
|
2175
|
+
<Row style={{ margin: '25px' }}>
|
|
2176
|
+
<Col xs={1} style={{ visibility: 'hidden' }}></Col>
|
|
2177
|
+
{mediaArray.slice(0, 5).map((attachment) => (
|
|
2178
|
+
<Col xs={2} key={attachment.id} style={{ minHeight: '200px' }}>
|
|
2179
|
+
{loadingStatus[attachment.id] && isImageArrayLoading ? <ThumbnailSkeleton />
|
|
2180
|
+
: attachment.toBeRemoved ? (
|
|
2181
|
+
<ThumbnailMarkRemoval
|
|
2182
|
+
undoId={attachment.id}
|
|
2183
|
+
handleUndo={handleUndo}
|
|
2184
|
+
/>
|
|
2185
|
+
) : <Thumbnail
|
|
2186
|
+
key={attachment.id}
|
|
2187
|
+
handleMarkForRemoval={handleMarkForRemoval}
|
|
2188
|
+
mediaId={attachment.id}
|
|
2189
|
+
src={mediaSrc[attachment.id]}
|
|
2190
|
+
alt={attachment.description}
|
|
2191
|
+
imageDescription={attachment.description}
|
|
2192
|
+
contentType={attachment.contentType}
|
|
2193
|
+
style={thumbnailStyle}
|
|
2194
|
+
isMarkedForRemoval={attachment.toBeRemoved}
|
|
2195
|
+
/>
|
|
2196
|
+
}
|
|
2197
|
+
</Col>
|
|
2198
|
+
))}
|
|
2199
|
+
</Row>
|
|
2200
|
+
<Row style={{ margin: '25px' }}>
|
|
2201
|
+
<Col xs={1} style={{ visibility: 'hidden' }}></Col>
|
|
2202
|
+
{mediaArray.slice(5, 10).map((attachment) => (
|
|
2203
|
+
<Col xs={2} key={attachment.id}>
|
|
2204
|
+
{loadingStatus[attachment.id] && isImageArrayLoading ? <ThumbnailSkeleton />
|
|
2205
|
+
: attachment.toBeRemoved ? (
|
|
2206
|
+
<ThumbnailMarkRemoval
|
|
2207
|
+
undoId={attachment.id}
|
|
2208
|
+
handleUndo={handleUndo}
|
|
2209
|
+
/>
|
|
2210
|
+
) : <Thumbnail
|
|
2211
|
+
key={attachment.id}
|
|
2212
|
+
handleMarkForRemoval={handleMarkForRemoval}
|
|
2213
|
+
mediaId={attachment.id}
|
|
2214
|
+
src={mediaSrc[attachment.id]}
|
|
2215
|
+
alt={attachment.description}
|
|
2216
|
+
imageDescription={attachment.description}
|
|
2217
|
+
contentType={attachment.contentType}
|
|
2218
|
+
style={thumbnailStyle}
|
|
2219
|
+
/>
|
|
2220
|
+
}
|
|
2221
|
+
</Col>
|
|
2222
|
+
))}
|
|
2223
|
+
</Row>
|
|
2224
|
+
|
|
2225
|
+
<Row style={{ margin: '25px' }}>
|
|
2226
|
+
<Col xs={1} style={{ visibility: 'hidden' }}></Col>
|
|
2227
|
+
{unsavedMediaArray.map((attachment) => (
|
|
2228
|
+
<Col xs={2} key={attachment.id}>
|
|
2229
|
+
<ThumbnailTempPreSave
|
|
2230
|
+
contentType={attachment.contentType}
|
|
2231
|
+
handleRemoveUnsavedMedia={handleRemoveUnsavedMedia}
|
|
2232
|
+
key={attachment.id}
|
|
2233
|
+
mediaId={attachment.id}
|
|
2234
|
+
src={attachment.filePreviewUrl}
|
|
2235
|
+
alt={attachment.description}
|
|
2236
|
+
imageDescription={attachment.description}
|
|
2237
|
+
style={thumbnailStyle}
|
|
2238
|
+
/>
|
|
2239
|
+
</Col>
|
|
2240
|
+
))}
|
|
2241
|
+
</Row>
|
|
2242
|
+
|
|
2243
|
+
<Row style={{ marginTop: '25px' }}>
|
|
2244
|
+
<Col xs={2}>
|
|
2245
|
+
<Button onClick={handleOpenModalAddMedia}>
|
|
2246
|
+
<FormattedMessage id="add-media-button" />
|
|
2247
|
+
</Button>
|
|
2248
|
+
</Col>
|
|
2249
|
+
</Row>
|
|
2250
|
+
</Accordion>
|
|
2251
|
+
<Accordion label={<FormattedMessage id="accordion-label-documents"/>}>
|
|
2252
|
+
<div>
|
|
2253
|
+
{documents.map((doc) => (
|
|
2254
|
+
<GetMedia
|
|
2255
|
+
context='document'
|
|
2256
|
+
key={doc.id}
|
|
2257
|
+
id={id}
|
|
2258
|
+
imageId={doc.id}
|
|
2259
|
+
mediaHandler={(mediaUrl) => handleMediaUrl(mediaUrl, doc.id)}
|
|
2260
|
+
/>
|
|
2261
|
+
))}
|
|
2262
|
+
</div>
|
|
2263
|
+
|
|
2264
|
+
{/* 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). */}
|
|
2265
|
+
<div>
|
|
2266
|
+
<Col xs={1} style={{ visibility: 'hidden' }}></Col>
|
|
2267
|
+
{sortedDocuments.map((doc) => (
|
|
2268
|
+
<Row xs={2} style={{ marginLeft: '15px' }} key={doc.id}>
|
|
2269
|
+
{doc.toBeRemoved ? (
|
|
2270
|
+
<div
|
|
2271
|
+
style={{
|
|
2272
|
+
display: 'flex',
|
|
2273
|
+
alignItems: 'center',
|
|
2274
|
+
marginTop: '10px',
|
|
2275
|
+
}}
|
|
2276
|
+
>
|
|
2277
|
+
<Button
|
|
2278
|
+
onClick={() => handleUndo(doc.id)}
|
|
2279
|
+
style={{ marginTop: '25px' }}
|
|
2280
|
+
buttonStyle='default'
|
|
2281
|
+
>
|
|
2282
|
+
<FormattedMessage id='undo-button' />
|
|
2283
|
+
</Button>
|
|
2284
|
+
</div>
|
|
2285
|
+
) : (
|
|
2286
|
+
<div
|
|
2287
|
+
style={{
|
|
2288
|
+
display: 'flex',
|
|
2289
|
+
alignItems: 'center',
|
|
2290
|
+
marginLeft: '10px',
|
|
2291
|
+
}}
|
|
2292
|
+
>
|
|
2293
|
+
<Button
|
|
2294
|
+
allowAnchorClick={true}
|
|
2295
|
+
href={mediaSrc[doc.id]}
|
|
2296
|
+
target='_blank'
|
|
2297
|
+
style={{ marginTop: '15px' }}
|
|
2298
|
+
>
|
|
2299
|
+
{doc.description}
|
|
2300
|
+
</Button>
|
|
2301
|
+
|
|
2302
|
+
{/* if most current document, render clock icon */}
|
|
2303
|
+
{mostCurrentTrespassDocIds.includes(doc.id) && (
|
|
2304
|
+
<span style={{ marginLeft: '8px' }}>
|
|
2305
|
+
<Icon icon='clock' size='small' />
|
|
2306
|
+
</span>
|
|
2307
|
+
)}
|
|
2308
|
+
|
|
2309
|
+
{/*
|
|
2310
|
+
Track application may or may not utilize delete trespass documents functionality.
|
|
2311
|
+
Possibly will be a configuration or specific perm in future release.
|
|
2312
|
+
Commenting out for now, rest of logic is present.
|
|
2313
|
+
*/}
|
|
2314
|
+
{/*
|
|
2315
|
+
<div style={{ marginLeft: '5px' }}>
|
|
2316
|
+
<button onClick={() => handleMarkForRemoval(doc.id)}>
|
|
2317
|
+
<Icon icon='trash'></Icon>
|
|
2318
|
+
</button>
|
|
2319
|
+
</div>
|
|
2320
|
+
*/}
|
|
2321
|
+
</div>
|
|
2322
|
+
)}
|
|
2323
|
+
</Row>
|
|
2324
|
+
))}
|
|
2325
|
+
</div>
|
|
2326
|
+
</Accordion>
|
|
2327
|
+
</AccordionSet>
|
|
2328
|
+
</Pane>)}
|
|
2329
|
+
</>
|
|
2330
|
+
)})
|
|
2331
|
+
</>
|
|
2332
|
+
);
|
|
2333
|
+
};
|
|
2334
|
+
export default EditPane;
|