@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,480 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import DOMPurify from 'dompurify';
|
|
5
|
+
import {
|
|
6
|
+
AccordionSet,
|
|
7
|
+
Accordion,
|
|
8
|
+
Button,
|
|
9
|
+
Col,
|
|
10
|
+
Datepicker,
|
|
11
|
+
Editor,
|
|
12
|
+
Modal,
|
|
13
|
+
ModalFooter,
|
|
14
|
+
Pane,
|
|
15
|
+
Paneset,
|
|
16
|
+
Row,
|
|
17
|
+
TextField,
|
|
18
|
+
} from '@folio/stripes/components';
|
|
19
|
+
import { useIncidents } from '../../contexts/IncidentContext';
|
|
20
|
+
import isValidDateFormat from './helpers/isValidDateFormat';
|
|
21
|
+
import stripHTML from './helpers/stripHTML';
|
|
22
|
+
import css from './ModalStyle.css';
|
|
23
|
+
|
|
24
|
+
const ModalCustomerDetails = ({
|
|
25
|
+
customerID,
|
|
26
|
+
allCustomers, //merged array from EditPane context [...selectedCustomers, formData.customers(instance custs)]
|
|
27
|
+
setAllCustomers // setter for allCustomers
|
|
28
|
+
}) => {
|
|
29
|
+
const {
|
|
30
|
+
isModalCustomerDetails,
|
|
31
|
+
closeModalCustomerDetails,
|
|
32
|
+
selectedCustomers, // customers array from CreatePane context
|
|
33
|
+
setSelectedCustomers, // setter for selectedCustomers
|
|
34
|
+
} = useIncidents();
|
|
35
|
+
|
|
36
|
+
const [customersArray, setCustomersArray] = useState([]);
|
|
37
|
+
const [workWithEdit, setWorkWithEdit] = useState(false);
|
|
38
|
+
const [isCustomerRegistered, setIsCustomerRegistered] = useState(true);
|
|
39
|
+
const [unregisteredNames, setUnregisteredNames] = useState({
|
|
40
|
+
firstName: '',
|
|
41
|
+
lastName: ''
|
|
42
|
+
});
|
|
43
|
+
const [useNames, setUseNames] = useState(false);
|
|
44
|
+
const [localCustomerDescription, setLocalCustomerDescription] = useState('');
|
|
45
|
+
const [localCustomerDetailsData, setLocalCustomerDetailsData] = useState({
|
|
46
|
+
sex: '',
|
|
47
|
+
race: '',
|
|
48
|
+
height: '',
|
|
49
|
+
weight: '',
|
|
50
|
+
hair: '',
|
|
51
|
+
eyes: '',
|
|
52
|
+
dateOfBirth: '',
|
|
53
|
+
streetAddress: '',
|
|
54
|
+
city: '',
|
|
55
|
+
state: '',
|
|
56
|
+
zipcode: '',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// set local customersArray based on context use
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (allCustomers) {
|
|
62
|
+
// setting if rendered via @EditPane
|
|
63
|
+
setWorkWithEdit(true);
|
|
64
|
+
setCustomersArray(allCustomers);
|
|
65
|
+
} else if (selectedCustomers) {
|
|
66
|
+
// setting of rendered via @CreatePane
|
|
67
|
+
setCustomersArray(selectedCustomers);
|
|
68
|
+
}
|
|
69
|
+
}, [selectedCustomers, allCustomers]);
|
|
70
|
+
|
|
71
|
+
// populate modal state with current customer data
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const currentCustomer = customersArray.find(
|
|
74
|
+
(cust) => cust.id === customerID
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (currentCustomer && currentCustomer.details) {
|
|
78
|
+
setLocalCustomerDetailsData({
|
|
79
|
+
sex: currentCustomer.details?.sex || '',
|
|
80
|
+
race: currentCustomer.details?.race || '',
|
|
81
|
+
height: currentCustomer.details?.height || '',
|
|
82
|
+
weight: currentCustomer.details?.weight || '',
|
|
83
|
+
hair: currentCustomer.details?.hair || '',
|
|
84
|
+
eyes: currentCustomer.details?.eyes || '',
|
|
85
|
+
dateOfBirth: currentCustomer.details?.dateOfBirth || '',
|
|
86
|
+
streetAddress: currentCustomer.details?.streetAddress || '',
|
|
87
|
+
city: currentCustomer.details?.city || '',
|
|
88
|
+
state: currentCustomer.details?.state || '',
|
|
89
|
+
zipcode: currentCustomer.details?.zipcode || '',
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
setLocalCustomerDetailsData({
|
|
93
|
+
sex: '',
|
|
94
|
+
race: '',
|
|
95
|
+
height: '',
|
|
96
|
+
weight: '',
|
|
97
|
+
hair: '',
|
|
98
|
+
eyes: '',
|
|
99
|
+
dateOfBirth: '',
|
|
100
|
+
streetAddress: '',
|
|
101
|
+
city: '',
|
|
102
|
+
state: '',
|
|
103
|
+
zipcode: '',
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (currentCustomer && currentCustomer.description) {
|
|
108
|
+
setLocalCustomerDescription(currentCustomer.description || '');
|
|
109
|
+
} else {
|
|
110
|
+
setLocalCustomerDescription('');
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (currentCustomer && currentCustomer.registered === false) {
|
|
114
|
+
setIsCustomerRegistered(false) // setter for no require customer 'description'
|
|
115
|
+
// console.log("is NOT registered block ran")
|
|
116
|
+
setUseNames(true) //if not registered, use instance name values and not associated key name
|
|
117
|
+
setUnregisteredNames({
|
|
118
|
+
firstName: currentCustomer.firstName,
|
|
119
|
+
lastName: currentCustomer.lastName
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
// console.log("IS registered block ran")
|
|
123
|
+
setIsCustomerRegistered(true)
|
|
124
|
+
setUseNames(false); // hide the name fields if registered
|
|
125
|
+
setUnregisteredNames({
|
|
126
|
+
firstName: '',
|
|
127
|
+
lastName: '',
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
}, [isModalCustomerDetails, customerID, customersArray]);
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
const handleDetailsChange = (event) => {
|
|
134
|
+
const { name, value } = event.target;
|
|
135
|
+
setLocalCustomerDetailsData((prevData) => ({
|
|
136
|
+
...prevData,
|
|
137
|
+
[name]: value,
|
|
138
|
+
}));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const handleDescriptionChange = (content) => {
|
|
142
|
+
const sanitizedContent = DOMPurify.sanitize(content);
|
|
143
|
+
setLocalCustomerDescription(sanitizedContent);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const isFormValid = () => {
|
|
147
|
+
const isDateOfBirthValid = localCustomerDetailsData.dateOfBirth === '' ||
|
|
148
|
+
isValidDateFormat(localCustomerDetailsData.dateOfBirth);
|
|
149
|
+
|
|
150
|
+
const isEditorValid = isCustomerRegistered || (localCustomerDescription && stripHTML(localCustomerDescription));
|
|
151
|
+
|
|
152
|
+
return isDateOfBirthValid && isEditorValid;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const handleUnregisteredNames = (event) => {
|
|
156
|
+
const { name, value } = event.target;
|
|
157
|
+
setUnregisteredNames((prevData) => ({
|
|
158
|
+
...prevData,
|
|
159
|
+
[name]: value,
|
|
160
|
+
}))
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const normalizeDescription = (html) => {
|
|
164
|
+
const raw = (html || '').trim();
|
|
165
|
+
return stripHTML(raw) ? raw : undefined;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const handleSave = () => {
|
|
169
|
+
const updatedCustomerArray = customersArray.map((cust) => {
|
|
170
|
+
if (cust.id === customerID) {
|
|
171
|
+
const cleanedDetails = Object.entries(localCustomerDetailsData).reduce((acc, [k, v]) => {
|
|
172
|
+
const trimmed = typeof v === 'string' ? v.trim() : v;
|
|
173
|
+
if (trimmed !== '' && trimmed !== null && trimmed !== undefined) {
|
|
174
|
+
acc[k] = trimmed;
|
|
175
|
+
}
|
|
176
|
+
return acc;
|
|
177
|
+
}, {});
|
|
178
|
+
|
|
179
|
+
// only include details if it has non-empty fields
|
|
180
|
+
const hasDetails = Object.keys(cleanedDetails).length > 0;
|
|
181
|
+
|
|
182
|
+
const updatedCustomer = {
|
|
183
|
+
...cust,
|
|
184
|
+
firstName: useNames ? unregisteredNames.firstName : cust.firstName,
|
|
185
|
+
lastName: useNames ? unregisteredNames.lastName : cust.lastName,
|
|
186
|
+
...(hasDetails ? { details: cleanedDetails } : {})
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const normalizeDesc = normalizeDescription(localCustomerDescription);
|
|
190
|
+
if (normalizeDesc) {
|
|
191
|
+
updatedCustomer.description = normalizeDesc;
|
|
192
|
+
} else {
|
|
193
|
+
delete updatedCustomer.description
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
if (!hasDetails && 'details' in updatedCustomer) {
|
|
197
|
+
delete updatedCustomer.details;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return updatedCustomer;
|
|
201
|
+
};
|
|
202
|
+
return cust;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (workWithEdit) {
|
|
206
|
+
// setting via EditPane
|
|
207
|
+
setAllCustomers(updatedCustomerArray);
|
|
208
|
+
} else {
|
|
209
|
+
// setting via CreatePane, using setSelectedCustomers
|
|
210
|
+
setSelectedCustomers(updatedCustomerArray);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
resetModalState();
|
|
214
|
+
closeModalCustomerDetails();
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const resetModalState = () => {
|
|
218
|
+
setLocalCustomerDescription('');
|
|
219
|
+
setLocalCustomerDetailsData({
|
|
220
|
+
sex: '',
|
|
221
|
+
race: '',
|
|
222
|
+
height: '',
|
|
223
|
+
weight: '',
|
|
224
|
+
hair: '',
|
|
225
|
+
eyes: '',
|
|
226
|
+
dateOfBirth: '',
|
|
227
|
+
streetAddress: '',
|
|
228
|
+
city: '',
|
|
229
|
+
state: '',
|
|
230
|
+
zipcode: '',
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
setUnregisteredNames({ firstName: '', lastName: '' });
|
|
234
|
+
setWorkWithEdit(false);
|
|
235
|
+
setUseNames(false);
|
|
236
|
+
setIsCustomerRegistered(true);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const handleCloseDismiss = () => {
|
|
240
|
+
setIsCustomerRegistered(true);
|
|
241
|
+
resetModalState();
|
|
242
|
+
closeModalCustomerDetails();
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (!isModalCustomerDetails) {
|
|
246
|
+
return null;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const editorModules = {
|
|
250
|
+
toolbar: [
|
|
251
|
+
[{ 'header': [1, 2, false] }],
|
|
252
|
+
['bold', 'italic', 'underline'],
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const footer = (
|
|
257
|
+
<ModalFooter>
|
|
258
|
+
<Button
|
|
259
|
+
onClick={handleSave}
|
|
260
|
+
buttonStyle="primary"
|
|
261
|
+
marginBottom0
|
|
262
|
+
disabled={!isFormValid()}
|
|
263
|
+
>
|
|
264
|
+
<FormattedMessage id="close-continue-button" />
|
|
265
|
+
</Button>
|
|
266
|
+
<Button onClick={handleCloseDismiss}>
|
|
267
|
+
<FormattedMessage id="cancel-button" />
|
|
268
|
+
</Button>
|
|
269
|
+
</ModalFooter>
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<Modal
|
|
274
|
+
style={{
|
|
275
|
+
minHeight: '250px',
|
|
276
|
+
height: '80%', // allows modal to grow/shrink based on content
|
|
277
|
+
maxHeight: '300vh',
|
|
278
|
+
maxWidth: '300vw', // modal width responsive to viewport width
|
|
279
|
+
width: '70%' // modal width adjusts based on content and window size
|
|
280
|
+
}}
|
|
281
|
+
open
|
|
282
|
+
dismissible
|
|
283
|
+
onClose={handleCloseDismiss}
|
|
284
|
+
closeOnBackgroundClick
|
|
285
|
+
label={<FormattedMessage id="modal-customer-details-label" />}
|
|
286
|
+
size="large"
|
|
287
|
+
footer={footer}
|
|
288
|
+
contentClass={css.modalContent}
|
|
289
|
+
>
|
|
290
|
+
<Paneset>
|
|
291
|
+
<Pane defaultWidth="100%">
|
|
292
|
+
<AccordionSet>
|
|
293
|
+
<Accordion
|
|
294
|
+
label={
|
|
295
|
+
<FormattedMessage id="modal-customer-details.accordion-label-customer-description" />
|
|
296
|
+
}
|
|
297
|
+
>
|
|
298
|
+
{useNames && (
|
|
299
|
+
<>
|
|
300
|
+
<Row>
|
|
301
|
+
<Col xs={3}>
|
|
302
|
+
<TextField
|
|
303
|
+
onChange={handleUnregisteredNames}
|
|
304
|
+
value={unregisteredNames.firstName}
|
|
305
|
+
name="firstName"
|
|
306
|
+
label={
|
|
307
|
+
<FormattedMessage id="modal-customer-details.textField-first-name-label" />
|
|
308
|
+
}
|
|
309
|
+
/>
|
|
310
|
+
|
|
311
|
+
</Col>
|
|
312
|
+
</Row>
|
|
313
|
+
<Row>
|
|
314
|
+
<Col xs={3}>
|
|
315
|
+
<TextField
|
|
316
|
+
onChange={handleUnregisteredNames}
|
|
317
|
+
value={unregisteredNames.lastName}
|
|
318
|
+
name="lastName"
|
|
319
|
+
label={
|
|
320
|
+
<FormattedMessage id="modal-customer-details.textField-last-name-label" />
|
|
321
|
+
}
|
|
322
|
+
/>
|
|
323
|
+
</Col>
|
|
324
|
+
</Row>
|
|
325
|
+
</>
|
|
326
|
+
)}
|
|
327
|
+
|
|
328
|
+
<Row>
|
|
329
|
+
<Col xs={6}>
|
|
330
|
+
<Editor
|
|
331
|
+
required={!isCustomerRegistered}
|
|
332
|
+
label={
|
|
333
|
+
<FormattedMessage id="modal-customer-details.textArea-description-of-customer-label" />
|
|
334
|
+
}
|
|
335
|
+
value={localCustomerDescription}
|
|
336
|
+
onChange={handleDescriptionChange}
|
|
337
|
+
modules={editorModules}
|
|
338
|
+
/>
|
|
339
|
+
</Col>
|
|
340
|
+
</Row>
|
|
341
|
+
</Accordion>
|
|
342
|
+
<Accordion
|
|
343
|
+
label={
|
|
344
|
+
<FormattedMessage id="modal-customer-details.accordion-identity-details"/>
|
|
345
|
+
}
|
|
346
|
+
>
|
|
347
|
+
<Row>
|
|
348
|
+
<Col xs={2}>
|
|
349
|
+
<TextField
|
|
350
|
+
label={<FormattedMessage id="modal-customer-details-sex" />}
|
|
351
|
+
name="sex"
|
|
352
|
+
value={localCustomerDetailsData.sex}
|
|
353
|
+
onChange={handleDetailsChange}
|
|
354
|
+
/>
|
|
355
|
+
</Col>
|
|
356
|
+
<Col xs={2}>
|
|
357
|
+
<TextField
|
|
358
|
+
label={
|
|
359
|
+
<FormattedMessage id="modal-customer-details-race" />
|
|
360
|
+
}
|
|
361
|
+
name="race"
|
|
362
|
+
value={localCustomerDetailsData.race}
|
|
363
|
+
onChange={handleDetailsChange}
|
|
364
|
+
/>
|
|
365
|
+
</Col>
|
|
366
|
+
<Col xs={2}>
|
|
367
|
+
<TextField
|
|
368
|
+
label={
|
|
369
|
+
<FormattedMessage id="modal-customer-details-height" />
|
|
370
|
+
}
|
|
371
|
+
name="height"
|
|
372
|
+
value={localCustomerDetailsData.height}
|
|
373
|
+
onChange={handleDetailsChange}
|
|
374
|
+
/>
|
|
375
|
+
</Col>
|
|
376
|
+
</Row>
|
|
377
|
+
|
|
378
|
+
<Row>
|
|
379
|
+
<Col xs={2}>
|
|
380
|
+
<TextField
|
|
381
|
+
label={
|
|
382
|
+
<FormattedMessage id="modal-customer-details-weight" />
|
|
383
|
+
}
|
|
384
|
+
name="weight"
|
|
385
|
+
value={localCustomerDetailsData.weight}
|
|
386
|
+
onChange={handleDetailsChange}
|
|
387
|
+
/>
|
|
388
|
+
</Col>
|
|
389
|
+
<Col xs={2}>
|
|
390
|
+
<TextField
|
|
391
|
+
label={
|
|
392
|
+
<FormattedMessage id="modal-customer-details-hair" />
|
|
393
|
+
}
|
|
394
|
+
name="hair"
|
|
395
|
+
value={localCustomerDetailsData.hair}
|
|
396
|
+
onChange={handleDetailsChange}
|
|
397
|
+
/>
|
|
398
|
+
</Col>
|
|
399
|
+
<Col xs={2}>
|
|
400
|
+
<TextField
|
|
401
|
+
label={
|
|
402
|
+
<FormattedMessage id="modal-customer-details-eyes" />
|
|
403
|
+
}
|
|
404
|
+
name="eyes"
|
|
405
|
+
value={localCustomerDetailsData.eyes}
|
|
406
|
+
onChange={handleDetailsChange}
|
|
407
|
+
/>
|
|
408
|
+
</Col>
|
|
409
|
+
</Row>
|
|
410
|
+
<Row>
|
|
411
|
+
<Col xs={3}>
|
|
412
|
+
<Datepicker
|
|
413
|
+
label={
|
|
414
|
+
<FormattedMessage id="modal-customer-details-date-of-birth" />
|
|
415
|
+
}
|
|
416
|
+
name="dateOfBirth"
|
|
417
|
+
value={localCustomerDetailsData.dateOfBirth}
|
|
418
|
+
onChange={handleDetailsChange}
|
|
419
|
+
/>
|
|
420
|
+
</Col>
|
|
421
|
+
</Row>
|
|
422
|
+
<Row>
|
|
423
|
+
<Col xs={4}>
|
|
424
|
+
<TextField
|
|
425
|
+
label={
|
|
426
|
+
<FormattedMessage id="modal-customer-details-street-address" />
|
|
427
|
+
}
|
|
428
|
+
name="streetAddress"
|
|
429
|
+
value={localCustomerDetailsData.streetAddress}
|
|
430
|
+
onChange={handleDetailsChange}
|
|
431
|
+
/>
|
|
432
|
+
</Col>
|
|
433
|
+
<Col xs={2}>
|
|
434
|
+
<TextField
|
|
435
|
+
label={
|
|
436
|
+
<FormattedMessage id="modal-customer-details-city" />
|
|
437
|
+
}
|
|
438
|
+
name="city"
|
|
439
|
+
value={localCustomerDetailsData.city}
|
|
440
|
+
onChange={handleDetailsChange}
|
|
441
|
+
/>
|
|
442
|
+
</Col>
|
|
443
|
+
</Row>
|
|
444
|
+
<Row style={{ marginBottom: '100px' }}>
|
|
445
|
+
<Col xs={2}>
|
|
446
|
+
<TextField
|
|
447
|
+
label={
|
|
448
|
+
<FormattedMessage id="modal-customer-details-state" />
|
|
449
|
+
}
|
|
450
|
+
name="state"
|
|
451
|
+
value={localCustomerDetailsData.state}
|
|
452
|
+
onChange={handleDetailsChange}
|
|
453
|
+
/>
|
|
454
|
+
</Col>
|
|
455
|
+
<Col xs={2}>
|
|
456
|
+
<TextField
|
|
457
|
+
label={
|
|
458
|
+
<FormattedMessage id="modal-customer-details-zipcode" />
|
|
459
|
+
}
|
|
460
|
+
name="zipcode"
|
|
461
|
+
value={localCustomerDetailsData.zipcode}
|
|
462
|
+
onChange={handleDetailsChange}
|
|
463
|
+
/>
|
|
464
|
+
</Col>
|
|
465
|
+
</Row>
|
|
466
|
+
</Accordion>
|
|
467
|
+
</AccordionSet>
|
|
468
|
+
</Pane>
|
|
469
|
+
</Paneset>
|
|
470
|
+
</Modal>
|
|
471
|
+
);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
ModalCustomerDetails.propTypes = {
|
|
475
|
+
customerID: PropTypes.string.isRequired,
|
|
476
|
+
customersList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
477
|
+
setCustomersList: PropTypes.func.isRequired,
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
export default ModalCustomerDetails;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import { act } from 'react-dom/test-utils';
|
|
4
|
+
import ModalCustomerDetails from './ModalCustomerDetails';
|
|
5
|
+
|
|
6
|
+
// --- Mocks for External Dependencies ---
|
|
7
|
+
|
|
8
|
+
// Mock react-intl: FormattedMessage renders the id.
|
|
9
|
+
jest.mock('react-intl', () => ({
|
|
10
|
+
FormattedMessage: (props) => <span>{props.id}</span>,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// Mock @folio/stripes/components with minimal implementations.
|
|
14
|
+
jest.mock('@folio/stripes/components', () => {
|
|
15
|
+
const React = require('react');
|
|
16
|
+
return {
|
|
17
|
+
AccordionSet: (props) => <div {...props}>{props.children}</div>,
|
|
18
|
+
Accordion: (props) => <div {...props}>{props.children}</div>,
|
|
19
|
+
Button: (props) => <button {...props}>{props.children}</button>,
|
|
20
|
+
Col: (props) => <div {...props}>{props.children}</div>,
|
|
21
|
+
Datepicker: (props) => <input type="date" {...props} />,
|
|
22
|
+
Editor: (props) => <textarea {...props} />,
|
|
23
|
+
Modal: (props) => <div {...props}>{props.children}</div>,
|
|
24
|
+
ModalFooter: (props) => <div {...props}>{props.children}</div>,
|
|
25
|
+
Paneset: (props) => <div {...props}>{props.children}</div>,
|
|
26
|
+
Pane: (props) => <div {...props}>{props.children}</div>,
|
|
27
|
+
Row: (props) => <div {...props}>{props.children}</div>,
|
|
28
|
+
TextField: (props) => <input type="text" {...props} />,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Mock helper functions.
|
|
33
|
+
jest.mock('./helpers/isValidDateFormat', () => jest.fn(() => true));
|
|
34
|
+
jest.mock('./helpers/stripHTML', () => jest.fn((str) => str));
|
|
35
|
+
|
|
36
|
+
// --- Mocks for the Incident Context ---
|
|
37
|
+
// Define a stable context object inside the module factory to avoid re-render loops.
|
|
38
|
+
jest.mock('../../contexts/IncidentContext', () => {
|
|
39
|
+
const stableContext = {
|
|
40
|
+
isModalCustomerDetails: true, // modal is open for our test
|
|
41
|
+
closeModalCustomerDetails: jest.fn(),
|
|
42
|
+
selectedCustomers: [
|
|
43
|
+
{ id: 'cust-1', firstName: 'John', lastName: 'Doe', registered: false }
|
|
44
|
+
],
|
|
45
|
+
setSelectedCustomers: jest.fn(),
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
useIncidents: () => stableContext,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// --- Test Props ---
|
|
53
|
+
// Even though the PropTypes define customersList and setCustomersList,
|
|
54
|
+
// the component destructures props as { customerID, allCustomers, setAllCustomers }.
|
|
55
|
+
// Here we supply sample props for testing.
|
|
56
|
+
const sampleCustomer = {
|
|
57
|
+
id: 'cust-1',
|
|
58
|
+
firstName: 'John',
|
|
59
|
+
lastName: 'Doe',
|
|
60
|
+
registered: false,
|
|
61
|
+
details: {
|
|
62
|
+
sex: '',
|
|
63
|
+
race: '',
|
|
64
|
+
height: '',
|
|
65
|
+
weight: '',
|
|
66
|
+
hair: '',
|
|
67
|
+
eyes: '',
|
|
68
|
+
dateOfBirth: '',
|
|
69
|
+
streetAddress: '',
|
|
70
|
+
city: '',
|
|
71
|
+
state: '',
|
|
72
|
+
zipcode: '',
|
|
73
|
+
},
|
|
74
|
+
description: 'Test description',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const sampleProps = {
|
|
78
|
+
customerID: 'cust-1',
|
|
79
|
+
allCustomers: [sampleCustomer],
|
|
80
|
+
setAllCustomers: jest.fn(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// --- Test Setup ---
|
|
84
|
+
let container = null;
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
container = document.createElement('div');
|
|
87
|
+
document.body.appendChild(container);
|
|
88
|
+
});
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
document.body.removeChild(container);
|
|
91
|
+
container = null;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// --- Test Cases ---
|
|
95
|
+
|
|
96
|
+
// Test case 1: When isModalCustomerDetails is true, the modal renders.
|
|
97
|
+
test('ModalCustomerDetails renders correctly when open (snapshot test)', () => {
|
|
98
|
+
act(() => {
|
|
99
|
+
ReactDOM.render(<ModalCustomerDetails {...sampleProps} />, container);
|
|
100
|
+
});
|
|
101
|
+
expect(container.innerHTML).toMatchSnapshot();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Test case 2: When isModalCustomerDetails is false, the component returns null.
|
|
105
|
+
test('ModalCustomerDetails returns null when closed', () => {
|
|
106
|
+
// Retrieve the stable context and set isModalCustomerDetails to false.
|
|
107
|
+
const { useIncidents } = require('../../contexts/IncidentContext');
|
|
108
|
+
const stableContext = useIncidents();
|
|
109
|
+
stableContext.isModalCustomerDetails = false; // simulate modal closed
|
|
110
|
+
|
|
111
|
+
act(() => {
|
|
112
|
+
ReactDOM.render(<ModalCustomerDetails {...sampleProps} />, container);
|
|
113
|
+
});
|
|
114
|
+
// If the modal is closed, the component should render nothing.
|
|
115
|
+
expect(container.innerHTML).toBe('');
|
|
116
|
+
});
|