@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,469 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { FormattedMessage } from 'react-intl';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
Col,
|
|
6
|
+
Modal,
|
|
7
|
+
Pane,
|
|
8
|
+
Paneset,
|
|
9
|
+
MessageBanner,
|
|
10
|
+
ModalFooter,
|
|
11
|
+
Row,
|
|
12
|
+
TextField,
|
|
13
|
+
} from '@folio/stripes/components';
|
|
14
|
+
import css from './ModalStyle.css';
|
|
15
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
16
|
+
import { useIncidents } from '../../contexts/IncidentContext';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const ModalCustomWitness = ({
|
|
20
|
+
custWitViewObj,
|
|
21
|
+
custWitEditID,
|
|
22
|
+
custWitEditObj,
|
|
23
|
+
setCustWitEditObj,
|
|
24
|
+
setFormData,
|
|
25
|
+
context,
|
|
26
|
+
}) => {
|
|
27
|
+
const {
|
|
28
|
+
isModalCustomWitness,
|
|
29
|
+
closeModalCustomWitness,
|
|
30
|
+
setSelectedWitnesses,
|
|
31
|
+
selectedWitnesses
|
|
32
|
+
} = useIncidents();
|
|
33
|
+
|
|
34
|
+
const [customWitForm, setCustomWitForm] = useState({
|
|
35
|
+
firstName: '',
|
|
36
|
+
lastName: '',
|
|
37
|
+
role: '',
|
|
38
|
+
phone: '',
|
|
39
|
+
email: ''
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// context of edit custom witness via CreatePane (selectedWitnesses)
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (custWitEditID) {
|
|
45
|
+
const witnessToEdit = selectedWitnesses.find(wit => wit.id === custWitEditID);
|
|
46
|
+
if (witnessToEdit) {
|
|
47
|
+
setCustomWitForm({
|
|
48
|
+
firstName: witnessToEdit.firstName || '',
|
|
49
|
+
lastName: witnessToEdit.lastName || '',
|
|
50
|
+
role: witnessToEdit.role || '',
|
|
51
|
+
phone: witnessToEdit.phone || '',
|
|
52
|
+
email: witnessToEdit.email || ''
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}, [custWitEditID, selectedWitnesses]);
|
|
57
|
+
|
|
58
|
+
// context of view custom witness
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (custWitViewObj) {
|
|
61
|
+
setCustomWitForm({
|
|
62
|
+
firstName: custWitViewObj.firstName || '',
|
|
63
|
+
lastName: custWitViewObj.lastName || '',
|
|
64
|
+
role: custWitViewObj.role || '',
|
|
65
|
+
phone: custWitViewObj.phone || '',
|
|
66
|
+
email: custWitViewObj.email || ''
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}, [custWitViewObj]);
|
|
70
|
+
|
|
71
|
+
// context of edit custom witness
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (custWitEditObj) {
|
|
74
|
+
setCustomWitForm({
|
|
75
|
+
id: custWitEditObj.id || '',
|
|
76
|
+
firstName: custWitEditObj.firstName || '',
|
|
77
|
+
lastName: custWitEditObj.lastName || '',
|
|
78
|
+
role: custWitEditObj.role || '',
|
|
79
|
+
phone: custWitEditObj.phone || '',
|
|
80
|
+
email: custWitEditObj.email || '',
|
|
81
|
+
isCustom: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}, [custWitEditObj]);
|
|
85
|
+
|
|
86
|
+
const [showPhoneError, setShowPhoneError] = useState(false);
|
|
87
|
+
const [showEmailError, setShowEmailError] = useState(false);
|
|
88
|
+
|
|
89
|
+
const handleChange = (event) => {
|
|
90
|
+
const { name, value } = event.target;
|
|
91
|
+
setCustomWitForm((prev) => ({
|
|
92
|
+
...prev,
|
|
93
|
+
[name]: value,
|
|
94
|
+
}));
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleCancel = () => {
|
|
98
|
+
setCustomWitForm({
|
|
99
|
+
firstName: '',
|
|
100
|
+
lastName: '',
|
|
101
|
+
role: '',
|
|
102
|
+
phone: '',
|
|
103
|
+
email: ''
|
|
104
|
+
});
|
|
105
|
+
// setCustWitEditID();
|
|
106
|
+
closeModalCustomWitness();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleCancelDetails = () => {
|
|
110
|
+
closeModalCustomWitness();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleOnEntered =() => {
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
setShowPhoneError(false);
|
|
116
|
+
setShowEmailError(false);
|
|
117
|
+
}, 4000)
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const validatePhoneNumber = (phone) => {
|
|
121
|
+
// allow: 1234567890, 123-456-7890, 123 456 7890,
|
|
122
|
+
// (123)456-7890, (123) 456-7890, 11234567890,
|
|
123
|
+
// +11234567890, +1 123 456 7890, +1-123-456-7890
|
|
124
|
+
// not allow: too few digits, extra chars, leading zero country code,
|
|
125
|
+
// incorrect placement of parens, more than 4 digits in last section
|
|
126
|
+
const phoneRegex = /^(\+?[1-9]{1,2})?(\s|-)?\(?\d{3}\)?(\s|-)?\d{3}(\s|-)?\d{4}$/;
|
|
127
|
+
return phoneRegex.test(phone)
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const validateEmail = (email) => {
|
|
131
|
+
// ensure local is one or more chars that are not space or @,
|
|
132
|
+
// ensure exactly one @,
|
|
133
|
+
// ensure domain one or more chars not space or @,
|
|
134
|
+
// ensure literal '.',
|
|
135
|
+
// ensure TLD 2-6 chars
|
|
136
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,6}$/;
|
|
137
|
+
return emailRegex.test(email);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const isFormDataPresent = () => {
|
|
141
|
+
const isFirstNameValid = customWitForm.firstName && customWitForm.firstName !== '';
|
|
142
|
+
const isLastNameValid = customWitForm.lastName && customWitForm.lastName !== '';
|
|
143
|
+
return isFirstNameValid && isLastNameValid;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// handle edit for uninstantiated (is local state) custom witness via CreatePane or EditPane
|
|
147
|
+
const handleSave = () => {
|
|
148
|
+
const phoneNumber = customWitForm.phone.trim();
|
|
149
|
+
const email = customWitForm.email.trim();
|
|
150
|
+
let hasError = false;
|
|
151
|
+
if (!validatePhoneNumber(phoneNumber) && phoneNumber !== '') {
|
|
152
|
+
setShowPhoneError(true)
|
|
153
|
+
hasError = true;
|
|
154
|
+
} else {
|
|
155
|
+
setShowPhoneError(false);
|
|
156
|
+
};
|
|
157
|
+
if (!validateEmail(email) && email !== '') {
|
|
158
|
+
setShowEmailError(true)
|
|
159
|
+
hasError = true;
|
|
160
|
+
} else {
|
|
161
|
+
setShowEmailError(false);
|
|
162
|
+
};
|
|
163
|
+
if (hasError) return;
|
|
164
|
+
|
|
165
|
+
const readyCustomWitness = {
|
|
166
|
+
id: custWitEditID || uuidv4(),
|
|
167
|
+
isCustom: true,
|
|
168
|
+
firstName: customWitForm.firstName.trim(),
|
|
169
|
+
lastName: customWitForm.lastName.trim(),
|
|
170
|
+
...(customWitForm.role.trim() ? { role: customWitForm.role.trim() } : {}),
|
|
171
|
+
...(customWitForm.phone.trim() ? { phone: customWitForm.phone.trim() } : {}),
|
|
172
|
+
...(customWitForm.email.trim() ? { email: customWitForm.email.trim() } : {}),
|
|
173
|
+
};
|
|
174
|
+
setSelectedWitnesses(prevData => {
|
|
175
|
+
if (custWitEditID) {
|
|
176
|
+
return prevData.map(wit => wit.id === custWitEditID ?
|
|
177
|
+
readyCustomWitness : wit);
|
|
178
|
+
} else {
|
|
179
|
+
return [...prevData, readyCustomWitness];
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
setCustomWitForm({
|
|
183
|
+
firstName: '',
|
|
184
|
+
lastName: '',
|
|
185
|
+
role: '',
|
|
186
|
+
phone: '',
|
|
187
|
+
email: ''
|
|
188
|
+
});
|
|
189
|
+
closeModalCustomWitness();
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// handle add new
|
|
193
|
+
const handleAddNewAtEdit = () => {
|
|
194
|
+
const phoneNumber = customWitForm.phone.trim();
|
|
195
|
+
const email = customWitForm.email.trim();
|
|
196
|
+
let hasError = false;
|
|
197
|
+
if (!validatePhoneNumber(phoneNumber) && phoneNumber !== '') {
|
|
198
|
+
setShowPhoneError(true)
|
|
199
|
+
hasError = true;
|
|
200
|
+
} else {
|
|
201
|
+
setShowPhoneError(false);
|
|
202
|
+
};
|
|
203
|
+
if (!validateEmail(email) && email !== '') {
|
|
204
|
+
setShowEmailError(true)
|
|
205
|
+
hasError = true;
|
|
206
|
+
} else {
|
|
207
|
+
setShowEmailError(false);
|
|
208
|
+
};
|
|
209
|
+
if (hasError) return;
|
|
210
|
+
|
|
211
|
+
setSelectedWitnesses(prevData => {
|
|
212
|
+
return [
|
|
213
|
+
...prevData,
|
|
214
|
+
{
|
|
215
|
+
id: uuidv4(),
|
|
216
|
+
isCustom: true,
|
|
217
|
+
firstName: customWitForm.firstName.trim(),
|
|
218
|
+
lastName: customWitForm.lastName.trim(),
|
|
219
|
+
...(customWitForm.role.trim() ? { role: customWitForm.role.trim() } : {}),
|
|
220
|
+
...(customWitForm.phone.trim() ? { phone: customWitForm.phone.trim() } : {}),
|
|
221
|
+
...(customWitForm.email.trim() ? { email: customWitForm.email.trim() } : {}),
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
});
|
|
225
|
+
setCustomWitForm({
|
|
226
|
+
firstName: '',
|
|
227
|
+
lastName: '',
|
|
228
|
+
role: '',
|
|
229
|
+
phone: '',
|
|
230
|
+
email: ''
|
|
231
|
+
});
|
|
232
|
+
closeModalCustomWitness();
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// handle edit an instantiated custom witness via edit pane
|
|
236
|
+
const handleSaveEditInstantiated = () => {
|
|
237
|
+
const phoneNumber = customWitForm.phone;
|
|
238
|
+
const email = customWitForm.email;
|
|
239
|
+
let hasError = false;
|
|
240
|
+
|
|
241
|
+
if (!validatePhoneNumber(phoneNumber) && phoneNumber !== '') {
|
|
242
|
+
setShowPhoneError(true)
|
|
243
|
+
hasError = true;
|
|
244
|
+
} else {
|
|
245
|
+
setShowPhoneError(false);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (!validateEmail(email) && email !== '') {
|
|
249
|
+
setShowEmailError(true)
|
|
250
|
+
hasError = true;
|
|
251
|
+
} else {
|
|
252
|
+
setShowEmailError(false);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
if (hasError) return;
|
|
256
|
+
|
|
257
|
+
setFormData(prevFormData => ({
|
|
258
|
+
...prevFormData,
|
|
259
|
+
incidentWitnesses: prevFormData.incidentWitnesses.map(wit => wit.id === customWitForm.id ? {
|
|
260
|
+
...customWitForm,
|
|
261
|
+
isCustom: true,
|
|
262
|
+
id: customWitForm.id,
|
|
263
|
+
firstName: customWitForm.firstName.trim(),
|
|
264
|
+
lastName: customWitForm.lastName.trim(),
|
|
265
|
+
...(customWitForm.role.trim() ? { role: customWitForm.role.trim() } : {}),
|
|
266
|
+
...(customWitForm.phone.trim() ? { phone: customWitForm.phone.trim() } : {}),
|
|
267
|
+
...(customWitForm.email.trim() ? { email: customWitForm.email.trim() } : {}),
|
|
268
|
+
|
|
269
|
+
} : wit)
|
|
270
|
+
}));
|
|
271
|
+
setCustomWitForm({
|
|
272
|
+
firstName: '',
|
|
273
|
+
lastName: '',
|
|
274
|
+
role: '',
|
|
275
|
+
phone: '',
|
|
276
|
+
email: ''
|
|
277
|
+
});
|
|
278
|
+
setCustWitEditObj({}); // set to empty to resolve edit an unsaved custom wit
|
|
279
|
+
closeModalCustomWitness();
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const footer = (
|
|
283
|
+
<ModalFooter>
|
|
284
|
+
<Button
|
|
285
|
+
onClick={
|
|
286
|
+
context === 'editSavedCustomWitness' ? handleSaveEditInstantiated
|
|
287
|
+
: context === 'addCustomWitAtEdit' ? handleAddNewAtEdit
|
|
288
|
+
: handleSave}
|
|
289
|
+
buttonStyle="primary"
|
|
290
|
+
disabled={!isFormDataPresent()}
|
|
291
|
+
marginBottom0
|
|
292
|
+
>
|
|
293
|
+
<FormattedMessage id="close-continue-button" />
|
|
294
|
+
</Button>
|
|
295
|
+
<Button onClick={handleCancel}>
|
|
296
|
+
<FormattedMessage id="cancel-button" />
|
|
297
|
+
</Button>
|
|
298
|
+
</ModalFooter>
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const footerDetails = (
|
|
302
|
+
<ModalFooter>
|
|
303
|
+
<Button onClick={handleCancel}>
|
|
304
|
+
<FormattedMessage id="close-button" />
|
|
305
|
+
</Button>
|
|
306
|
+
</ModalFooter>
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const rowStyle = { marginTop: '10px', marginLeft: '20px'};
|
|
310
|
+
|
|
311
|
+
if (!isModalCustomWitness) {
|
|
312
|
+
return null;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
if (custWitViewObj) {
|
|
316
|
+
return (
|
|
317
|
+
<Modal
|
|
318
|
+
style={{ minHeight: '550px' }}
|
|
319
|
+
open
|
|
320
|
+
dismissible
|
|
321
|
+
closeOnBackgroundClick
|
|
322
|
+
label={<FormattedMessage id="modal-custom-witness-details-paneTitle" />}
|
|
323
|
+
size="medium"
|
|
324
|
+
onClose={handleCancelDetails}
|
|
325
|
+
footer={footerDetails}
|
|
326
|
+
contentClass={css.modalContent}
|
|
327
|
+
>
|
|
328
|
+
<Paneset>
|
|
329
|
+
<Pane defaultWidth='fill'>
|
|
330
|
+
<Row style={rowStyle}>
|
|
331
|
+
<Col xs={6}>
|
|
332
|
+
<strong>
|
|
333
|
+
<FormattedMessage id="modal-custom-witness-firstName-details"/>
|
|
334
|
+
</strong> {custWitViewObj.firstName}
|
|
335
|
+
</Col>
|
|
336
|
+
</Row>
|
|
337
|
+
<Row style={rowStyle}>
|
|
338
|
+
<Col xs={6}>
|
|
339
|
+
<strong>
|
|
340
|
+
<FormattedMessage id="modal-custom-witness-lastName-details"/>
|
|
341
|
+
</strong> {custWitViewObj.lastName}
|
|
342
|
+
</Col>
|
|
343
|
+
</Row>
|
|
344
|
+
<Row style={rowStyle}>
|
|
345
|
+
<Col xs={6}>
|
|
346
|
+
<strong>
|
|
347
|
+
<FormattedMessage id="modal-custom-witness-role-organization-details"/>
|
|
348
|
+
</strong> {custWitViewObj.role}
|
|
349
|
+
</Col>
|
|
350
|
+
</Row>
|
|
351
|
+
<Row style={rowStyle}>
|
|
352
|
+
<Col xs={6}>
|
|
353
|
+
<strong>
|
|
354
|
+
<FormattedMessage id="modal-custom-witness-phone-details"/>
|
|
355
|
+
</strong> {custWitViewObj.phone}
|
|
356
|
+
</Col>
|
|
357
|
+
</Row>
|
|
358
|
+
<Row style={rowStyle}>
|
|
359
|
+
<Col xs={6}>
|
|
360
|
+
<strong>
|
|
361
|
+
<FormattedMessage id="modal-custom-witness-email-details"/>
|
|
362
|
+
</strong> {custWitViewObj.email}
|
|
363
|
+
</Col>
|
|
364
|
+
</Row>
|
|
365
|
+
</Pane>
|
|
366
|
+
</Paneset>
|
|
367
|
+
</Modal>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<Modal
|
|
373
|
+
style={{
|
|
374
|
+
minHeight: '550px', // minimum height when the browser is full size
|
|
375
|
+
height: '80%', // allows modal to grow/shrink based on content
|
|
376
|
+
maxHeight: '300vh', // modal will never exceed 90% of viewport height
|
|
377
|
+
maxWidth: '300vw', // modal width responsive to viewport width
|
|
378
|
+
width: '60%' // modal width adjusts based on content and window size
|
|
379
|
+
}}
|
|
380
|
+
open
|
|
381
|
+
dismissible
|
|
382
|
+
closeOnBackgroundClick
|
|
383
|
+
label={<FormattedMessage id="modal-custom-witness-details-paneTitle" />}
|
|
384
|
+
size="large"
|
|
385
|
+
onClose={handleCancel}
|
|
386
|
+
footer={footer}
|
|
387
|
+
contentClass={css.modalContent}
|
|
388
|
+
>
|
|
389
|
+
<Paneset>
|
|
390
|
+
<Pane>
|
|
391
|
+
<Row style={rowStyle}>
|
|
392
|
+
<Col xs={3}>
|
|
393
|
+
<TextField
|
|
394
|
+
required
|
|
395
|
+
onChange={handleChange}
|
|
396
|
+
value={customWitForm.firstName}
|
|
397
|
+
name="firstName"
|
|
398
|
+
label={<FormattedMessage id="modal-custom-witness-firstName-label"/>}
|
|
399
|
+
// <FormattedMessage id="" />
|
|
400
|
+
/>
|
|
401
|
+
</Col>
|
|
402
|
+
</Row>
|
|
403
|
+
<Row style={rowStyle}>
|
|
404
|
+
<Col xs={3}>
|
|
405
|
+
<TextField
|
|
406
|
+
required
|
|
407
|
+
onChange={handleChange}
|
|
408
|
+
value={customWitForm.lastName}
|
|
409
|
+
name="lastName"
|
|
410
|
+
label={<FormattedMessage id="modal-custom-witness-lastName-label"/>}
|
|
411
|
+
/>
|
|
412
|
+
</Col>
|
|
413
|
+
</Row>
|
|
414
|
+
<Row style={rowStyle}>
|
|
415
|
+
<Col xs={3}>
|
|
416
|
+
<TextField
|
|
417
|
+
onChange={handleChange}
|
|
418
|
+
value={customWitForm.role}
|
|
419
|
+
name="role"
|
|
420
|
+
label={<FormattedMessage id="modal-custom-witness-role-organization-label"/>}
|
|
421
|
+
/>
|
|
422
|
+
</Col>
|
|
423
|
+
</Row>
|
|
424
|
+
<Row style={rowStyle}>
|
|
425
|
+
<Col xs={3}>
|
|
426
|
+
<TextField
|
|
427
|
+
onChange={handleChange}
|
|
428
|
+
value={customWitForm.phone}
|
|
429
|
+
name="phone"
|
|
430
|
+
label={<FormattedMessage id="modal-custom-witness-phone-label"/>}
|
|
431
|
+
/>
|
|
432
|
+
</Col>
|
|
433
|
+
<Col xs={3} style={{ marginTop: '18px'}}>
|
|
434
|
+
<MessageBanner
|
|
435
|
+
onEntered={handleOnEntered}
|
|
436
|
+
show={showPhoneError}
|
|
437
|
+
type='error'
|
|
438
|
+
>
|
|
439
|
+
<FormattedMessage id="modal-custom-witness-error-phone"/>
|
|
440
|
+
</MessageBanner>
|
|
441
|
+
</Col>
|
|
442
|
+
</Row>
|
|
443
|
+
|
|
444
|
+
<Row style={rowStyle}>
|
|
445
|
+
<Col xs={3}>
|
|
446
|
+
<TextField
|
|
447
|
+
onChange={handleChange}
|
|
448
|
+
value={customWitForm.email}
|
|
449
|
+
name="email"
|
|
450
|
+
label={<FormattedMessage id="modal-custom-witness-email-label"/>}
|
|
451
|
+
/>
|
|
452
|
+
</Col>
|
|
453
|
+
<Col xs={3} style={{ marginTop: '18px'}}>
|
|
454
|
+
<MessageBanner
|
|
455
|
+
onEntered={handleOnEntered}
|
|
456
|
+
show={showEmailError}
|
|
457
|
+
type='error'
|
|
458
|
+
>
|
|
459
|
+
<FormattedMessage id="modal-custom-witness-error-email"/>
|
|
460
|
+
</MessageBanner>
|
|
461
|
+
</Col>
|
|
462
|
+
</Row>
|
|
463
|
+
</Pane>
|
|
464
|
+
</Paneset>
|
|
465
|
+
</Modal>
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
export default ModalCustomWitness;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Define the mutable IncidentContext mock (allowed because its name begins with "mock")
|
|
2
|
+
const mockIncidentContext = {
|
|
3
|
+
isModalCustomWitness: true, // default: modal open for tests
|
|
4
|
+
closeModalCustomWitness: jest.fn(),
|
|
5
|
+
setSelectedWitnesses: jest.fn(),
|
|
6
|
+
selectedWitnesses: [],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// --- Mocks for the Incident Context ---
|
|
10
|
+
jest.mock('../../contexts/IncidentContext', () => ({
|
|
11
|
+
useIncidents: () => mockIncidentContext,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
import React from 'react';
|
|
15
|
+
import ReactDOM from 'react-dom';
|
|
16
|
+
import { act } from 'react-dom/test-utils';
|
|
17
|
+
import ModalCustomWitness from './ModalCustomWitness';
|
|
18
|
+
|
|
19
|
+
// --- Suppress known nonfatal warnings ---
|
|
20
|
+
const originalConsoleError = console.error;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
console.error = (...args) => {
|
|
23
|
+
if (
|
|
24
|
+
typeof args[0] === 'string' &&
|
|
25
|
+
(args[0].includes('ReactDOM.render is no longer supported') ||
|
|
26
|
+
args[0].includes('Invalid hook call') ||
|
|
27
|
+
args[0].includes('does not recognize the') ||
|
|
28
|
+
args[0].includes('Unknown event handler property') ||
|
|
29
|
+
args[0].includes('contentClass') ||
|
|
30
|
+
args[0].includes('defaultWidth'))
|
|
31
|
+
) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
originalConsoleError(...args);
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
console.error = originalConsoleError;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// --- Mocks for External Dependencies ---
|
|
42
|
+
|
|
43
|
+
// Mock react-intl so that FormattedMessage simply renders its id.
|
|
44
|
+
jest.mock('react-intl', () => ({
|
|
45
|
+
FormattedMessage: (props) => <span>{props.id}</span>,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// Update our mock for @folio/stripes/components so that TextField renders its label.
|
|
49
|
+
jest.mock('@folio/stripes/components', () => {
|
|
50
|
+
const React = require('react');
|
|
51
|
+
return {
|
|
52
|
+
Button: (props) => <button {...props}>{props.children}</button>,
|
|
53
|
+
Col: (props) => <div {...props}>{props.children}</div>,
|
|
54
|
+
Modal: (props) => <div {...props}>{props.children}</div>,
|
|
55
|
+
Paneset: (props) => <div {...props}>{props.children}</div>,
|
|
56
|
+
Pane: (props) => <div {...props}>{props.children}</div>,
|
|
57
|
+
ModalFooter: (props) => <div {...props}>{props.children}</div>,
|
|
58
|
+
Row: (props) => <div {...props}>{props.children}</div>,
|
|
59
|
+
// Modified TextField: renders a label and an input.
|
|
60
|
+
// Inside your jest.mock('@folio/stripes/components', ...) block:
|
|
61
|
+
TextField: (props) => {
|
|
62
|
+
// If props.label is an object and has a props.id (from FormattedMessage),
|
|
63
|
+
// then use that for rendering.
|
|
64
|
+
let labelText = '';
|
|
65
|
+
if (props.label) {
|
|
66
|
+
if (typeof props.label === 'object' && props.label.props && props.label.props.id) {
|
|
67
|
+
labelText = props.label.props.id;
|
|
68
|
+
} else {
|
|
69
|
+
labelText = props.label;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
{labelText && <label>{labelText}</label>}
|
|
75
|
+
<input type="text" {...props} />
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
// Render MessageBanner only if props.show is truthy.
|
|
80
|
+
MessageBanner: (props) => (props.show ? <div {...props}>{props.children}</div> : null),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Mock the CSS module.
|
|
85
|
+
jest.mock('./ModalStyle.css', () => ({}));
|
|
86
|
+
|
|
87
|
+
// --- Test Setup ---
|
|
88
|
+
let container = null;
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
container = document.createElement('div');
|
|
91
|
+
document.body.appendChild(container);
|
|
92
|
+
});
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
ReactDOM.unmountComponentAtNode(container);
|
|
95
|
+
container.remove();
|
|
96
|
+
container = null;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// --- Test Cases ---
|
|
100
|
+
describe('ModalCustomWitness', () => {
|
|
101
|
+
test('returns null when modal is closed', () => {
|
|
102
|
+
mockIncidentContext.isModalCustomWitness = false;
|
|
103
|
+
act(() => {
|
|
104
|
+
ReactDOM.render(<ModalCustomWitness />, container);
|
|
105
|
+
});
|
|
106
|
+
expect(container.innerHTML).toBe('');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('renders view mode when custWitViewObj is provided', () => {
|
|
110
|
+
mockIncidentContext.isModalCustomWitness = true;
|
|
111
|
+
const custWitViewObj = {
|
|
112
|
+
firstName: 'Alice',
|
|
113
|
+
lastName: 'Smith',
|
|
114
|
+
role: 'Manager',
|
|
115
|
+
phone: '123-456-7890',
|
|
116
|
+
email: 'alice@example.com',
|
|
117
|
+
};
|
|
118
|
+
act(() => {
|
|
119
|
+
ReactDOM.render(
|
|
120
|
+
<ModalCustomWitness custWitViewObj={custWitViewObj} />,
|
|
121
|
+
container
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
const text = container.textContent;
|
|
125
|
+
// Expect the view mode to render detail labels and witness data.
|
|
126
|
+
expect(text).toMatch(/modal-custom-witness-firstName-details/);
|
|
127
|
+
expect(text).toMatch(/modal-custom-witness-lastName-details/);
|
|
128
|
+
expect(text).toMatch(/Alice/);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('renders edit/add form when no custWitViewObj is provided', () => {
|
|
132
|
+
mockIncidentContext.isModalCustomWitness = true;
|
|
133
|
+
act(() => {
|
|
134
|
+
ReactDOM.render(
|
|
135
|
+
<ModalCustomWitness context="edit" />,
|
|
136
|
+
container
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
const text = container.textContent;
|
|
140
|
+
// Now the label should be rendered by our updated TextField mock.
|
|
141
|
+
expect(text).toMatch(/modal-custom-witness-firstName-label/);
|
|
142
|
+
expect(text).toMatch(/modal-custom-witness-lastName-label/);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|