@spokane-folio/security-incident 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/.eslintrc +32 -0
  2. package/.github/workflows/CODEOWNERS +8 -0
  3. package/.github/workflows/pr-validation.yml +44 -0
  4. package/.github/workflows/release.yml +64 -0
  5. package/.prettierrc +6 -0
  6. package/.stripesclirc +4 -0
  7. package/CHANGELOG.md +8 -0
  8. package/CONTRIBUTING.md +4 -0
  9. package/LICENSE +201 -0
  10. package/README.md +16 -0
  11. package/administrator-documentation/roles-and-permissions.md +65 -0
  12. package/administrator-documentation/track-settings-admin-guide-sketch.md +192 -0
  13. package/administrator-documentation/using-the-application.md +192 -0
  14. package/icons/app.png +0 -0
  15. package/icons/app.svg +1 -0
  16. package/icons/playButton.png +0 -0
  17. package/icons/profilePicThumbnail.png +0 -0
  18. package/jest.config.js +10 -0
  19. package/module-descriptor.json +75 -0
  20. package/output/service-worker.js +0 -0
  21. package/package.json +146 -0
  22. package/src/components/incidents/ColumnChooser.js +37 -0
  23. package/src/components/incidents/CreateMedia.js +132 -0
  24. package/src/components/incidents/CreatePane.js +1215 -0
  25. package/src/components/incidents/CreatePane.test.js +138 -0
  26. package/src/components/incidents/CreateReport.js +102 -0
  27. package/src/components/incidents/DetailsPane.js +1267 -0
  28. package/src/components/incidents/DetailsPane.test.js +150 -0
  29. package/src/components/incidents/EditPane.js +2334 -0
  30. package/src/components/incidents/EditPane.test.js +187 -0
  31. package/src/components/incidents/GetDetails.js +55 -0
  32. package/src/components/incidents/GetListDQLinkIncident.js +81 -0
  33. package/src/components/incidents/GetListDynamicQuery.js +66 -0
  34. package/src/components/incidents/GetLocations.js +57 -0
  35. package/src/components/incidents/GetMedia.js +98 -0
  36. package/src/components/incidents/GetName.js +111 -0
  37. package/src/components/incidents/GetNameCreatedBy.js +94 -0
  38. package/src/components/incidents/GetOrgLocaleSettings.js +61 -0
  39. package/src/components/incidents/GetPatronGroups.js +52 -0
  40. package/src/components/incidents/GetSelf.js +65 -0
  41. package/src/components/incidents/GetSummary.js +110 -0
  42. package/src/components/incidents/IncidentTypeCard.js +53 -0
  43. package/src/components/incidents/IncidentTypeCard.test.js +133 -0
  44. package/src/components/incidents/IncidentsPaneset.js +810 -0
  45. package/src/components/incidents/IncidentsPaneset.test.js +128 -0
  46. package/src/components/incidents/LinkedIncident.js +86 -0
  47. package/src/components/incidents/ModalAddMedia.js +262 -0
  48. package/src/components/incidents/ModalAddMedia.test.js +97 -0
  49. package/src/components/incidents/ModalAttentionDecOfService.js +111 -0
  50. package/src/components/incidents/ModalCustomWitness.js +469 -0
  51. package/src/components/incidents/ModalCustomWitness.test.js +147 -0
  52. package/src/components/incidents/ModalCustomerDetails.js +480 -0
  53. package/src/components/incidents/ModalCustomerDetails.test.js +116 -0
  54. package/src/components/incidents/ModalDescribeCustomer.js +361 -0
  55. package/src/components/incidents/ModalDescribeCustomer.test.js +156 -0
  56. package/src/components/incidents/ModalDirtyFormWarn.js +62 -0
  57. package/src/components/incidents/ModalLinkIncident.js +1213 -0
  58. package/src/components/incidents/ModalLinkIncidentStyle.css +32 -0
  59. package/src/components/incidents/ModalSelectIncidentTypes.js +178 -0
  60. package/src/components/incidents/ModalSelectIncidentTypes.test.js +273 -0
  61. package/src/components/incidents/ModalSelectKnownCustomer.js +395 -0
  62. package/src/components/incidents/ModalSelectWitness.js +406 -0
  63. package/src/components/incidents/ModalSelectWitness.test.js +308 -0
  64. package/src/components/incidents/ModalStyle.css +44 -0
  65. package/src/components/incidents/ModalTrespass.js +741 -0
  66. package/src/components/incidents/ModalViewCustomerDetails.js +241 -0
  67. package/src/components/incidents/ModalViewMedia.js +86 -0
  68. package/src/components/incidents/ModalViewTrespass.js +210 -0
  69. package/src/components/incidents/ResultsPane.js +437 -0
  70. package/src/components/incidents/ResultsPane.test.js +120 -0
  71. package/src/components/incidents/SearchCustomerOrWitness.js +108 -0
  72. package/src/components/incidents/Thumbnail.js +72 -0
  73. package/src/components/incidents/ThumbnailMarkRemoval.js +38 -0
  74. package/src/components/incidents/ThumbnailSkeleton.js +30 -0
  75. package/src/components/incidents/ThumbnailStyles.js +49 -0
  76. package/src/components/incidents/ThumbnailTempPreSave.js +71 -0
  77. package/src/components/incidents/UpdateReport.js +84 -0
  78. package/src/components/incidents/__snapshots__/CreatePane.test.js.snap +3 -0
  79. package/src/components/incidents/__snapshots__/DetailsPane.test.js.snap +3 -0
  80. package/src/components/incidents/__snapshots__/EditPane.test.js.snap +3 -0
  81. package/src/components/incidents/__snapshots__/IncidentTypeCard.test.js.snap +3 -0
  82. package/src/components/incidents/__snapshots__/IncidentsPaneset.test.js.snap +3 -0
  83. package/src/components/incidents/__snapshots__/ModalAddMedia.test.js.snap +3 -0
  84. package/src/components/incidents/__snapshots__/ModalCustomerDetails.test.js.snap +3 -0
  85. package/src/components/incidents/__snapshots__/ModalSelectWitness.test.js.snap +3 -0
  86. package/src/components/incidents/__snapshots__/ResultsPane.test.js.snap +3 -0
  87. package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.css +5 -0
  88. package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.js +51 -0
  89. package/src/components/incidents/helpers/ProfilePicture/isAValidURL.js +3 -0
  90. package/src/components/incidents/helpers/ProfilePicture/useProfilePicture.js +127 -0
  91. package/src/components/incidents/helpers/buildQueryString.js +28 -0
  92. package/src/components/incidents/helpers/cleanFormValues.js +53 -0
  93. package/src/components/incidents/helpers/computeEditedCustomers.js +124 -0
  94. package/src/components/incidents/helpers/convertDateIgnoringTZ.js +8 -0
  95. package/src/components/incidents/helpers/convertUTCISOToLocalePrettyTime.js +15 -0
  96. package/src/components/incidents/helpers/convertUTCISOToPrettyDate.js +19 -0
  97. package/src/components/incidents/helpers/decodeParamsToForm.js +20 -0
  98. package/src/components/incidents/helpers/deepNormalizeForComparison.js +39 -0
  99. package/src/components/incidents/helpers/extractFilterString.js +12 -0
  100. package/src/components/incidents/helpers/formatDateAndTimeToUTCISO.js +14 -0
  101. package/src/components/incidents/helpers/formatDateToUTCISO.js +14 -0
  102. package/src/components/incidents/helpers/formatTimeToUTCISO.js +28 -0
  103. package/src/components/incidents/helpers/getCurrentTime.js +20 -0
  104. package/src/components/incidents/helpers/getTodayDate.js +12 -0
  105. package/src/components/incidents/helpers/handlebarsHelpers.js +148 -0
  106. package/src/components/incidents/helpers/hasFormChangedAtCreate.js +50 -0
  107. package/src/components/incidents/helpers/hasTopLevelChangeAffectedDeclaration.js +90 -0
  108. package/src/components/incidents/helpers/hasTopLevelFormChanged.js +111 -0
  109. package/src/components/incidents/helpers/identifyCurrentTrespassDocs.js +109 -0
  110. package/src/components/incidents/helpers/isSameHtml.js +13 -0
  111. package/src/components/incidents/helpers/isValidDateFormat.js +14 -0
  112. package/src/components/incidents/helpers/isValidTimeInput.js +11 -0
  113. package/src/components/incidents/helpers/isValidUTCTimeFormat.js +14 -0
  114. package/src/components/incidents/helpers/parseMMDDYYYY.js +7 -0
  115. package/src/components/incidents/helpers/parseQueryString.js +16 -0
  116. package/src/components/incidents/helpers/sortTrespassDocuments.js +44 -0
  117. package/src/components/incidents/helpers/stripHTML.js +11 -0
  118. package/src/components/incidents/helpers/trespassDocUtils.js +197 -0
  119. package/src/components/incidents/helpers/validateTrespassDetails.js +37 -0
  120. package/src/components/incidents/usePersistedColModalLink.js +70 -0
  121. package/src/components/incidents/usePersistedColumns.js +70 -0
  122. package/src/components/incidents/usePersistedSort.js +23 -0
  123. package/src/components/incidents/usePersistedSortModalLink.js +23 -0
  124. package/src/contexts/IncidentContext.js +433 -0
  125. package/src/index.js +61 -0
  126. package/src/routes/Application.js +13 -0
  127. package/src/settings/GetIncidentCategories.js +56 -0
  128. package/src/settings/GetIncidentTypesDetails.js +88 -0
  129. package/src/settings/GetIncidentTypesIds.js +74 -0
  130. package/src/settings/GetLocationsInService.js +54 -0
  131. package/src/settings/GetSingleCustomLocationDetails.js +60 -0
  132. package/src/settings/GetSingleIncidentTypeDetails.js +60 -0
  133. package/src/settings/GetTrespassReasons.js +67 -0
  134. package/src/settings/GetTrespassTemplates.js +51 -0
  135. package/src/settings/IncidentCategoriesPane.js +285 -0
  136. package/src/settings/IncidentCategoriesPane.test.js +229 -0
  137. package/src/settings/IncidentTypeDetailsPane.js +215 -0
  138. package/src/settings/IncidentTypeDetailsPane.test.js +220 -0
  139. package/src/settings/IncidentTypeEditPane.js +211 -0
  140. package/src/settings/IncidentTypeEditPane.test.js +170 -0
  141. package/src/settings/IncidentTypesPaneset.js +167 -0
  142. package/src/settings/IncidentTypesPaneset.test.js +124 -0
  143. package/src/settings/LocationInServiceEditPane.js +320 -0
  144. package/src/settings/LocationsPaneset.js +415 -0
  145. package/src/settings/LocationsPaneset.test.js +106 -0
  146. package/src/settings/ModalDeleteCategory.js +47 -0
  147. package/src/settings/ModalDeleteIncidentType.js +49 -0
  148. package/src/settings/ModalDeleteLocationInService.js +49 -0
  149. package/src/settings/ModalDeleteTrespassReason.js +49 -0
  150. package/src/settings/ModalPreviewTrespassDoc.js +65 -0
  151. package/src/settings/ModalTrespassDocTokens.js +83 -0
  152. package/src/settings/NewIncidentTypePane.js +182 -0
  153. package/src/settings/PutIncidentType.js +60 -0
  154. package/src/settings/PutLocationsInService.js +52 -0
  155. package/src/settings/PutTrespassReasons.js +61 -0
  156. package/src/settings/PutTrespassTemplate.js +50 -0
  157. package/src/settings/TrespassDoc.css +17 -0
  158. package/src/settings/TrespassDocDetailsPane.js +215 -0
  159. package/src/settings/TrespassDocEditPane.js +538 -0
  160. package/src/settings/TrespassDocPaneset.js +581 -0
  161. package/src/settings/TrespassReasonDetailsPane.js +171 -0
  162. package/src/settings/TrespassReasonEditPane.js +221 -0
  163. package/src/settings/TrespassReasonsPaneset.js +282 -0
  164. package/src/settings/__snapshots__/IncidentCategoriesPane.test.js.snap +3 -0
  165. package/src/settings/__snapshots__/IncidentTypeDetailsPane.test.js.snap +3 -0
  166. package/src/settings/__snapshots__/IncidentTypeEditPane.test.js.snap +3 -0
  167. package/src/settings/__snapshots__/IncidentTypesPaneset.test.js.snap +3 -0
  168. package/src/settings/__snapshots__/LocationsPaneset.test.js.snap +3 -0
  169. package/src/settings/data/exampleJSON.json +92 -0
  170. package/src/settings/data/templateTokens.js +396 -0
  171. package/src/settings/helpers/alphabetize.js +18 -0
  172. package/src/settings/helpers/getCategoryTitleById.js +13 -0
  173. package/src/settings/helpers/makeId.js +15 -0
  174. package/src/settings/index.js +48 -0
  175. package/stripes.config.js +10 -0
  176. package/test/jest/__mock__/index.js +8 -0
  177. package/test/jest/__mock__/intl.mock.js +27 -0
  178. package/test/jest/__mock__/stripes.mock.js +26 -0
  179. package/test/jest/__mock__/stripesComponents.mock.js +151 -0
  180. package/test/jest/__mock__/stripesConfig.mock.js +1 -0
  181. package/test/jest/__mock__/stripesCore.mock.js +9 -0
  182. package/test/jest/__mock__/stripesIcon.mock.js +5 -0
  183. package/test/jest/__mock__/stripesSmartComponents.mock.js +7 -0
  184. package/test/jest/__mock__/stripesUtils.mock.js +3 -0
  185. package/test/jest/eslintrc.js +12 -0
  186. package/test/jest/setupFiles.js +5 -0
  187. package/translations/ui-security-incident/en_US.json +542 -0
  188. package/ui-module-acceptance-criteria.md +34 -0
@@ -0,0 +1,361 @@
1
+ import React, { useState } from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import DOMPurify from 'dompurify';
4
+ import {
5
+ AccordionSet,
6
+ Accordion,
7
+ Button,
8
+ Col,
9
+ Datepicker,
10
+ Editor,
11
+ Modal,
12
+ Pane,
13
+ Paneset,
14
+ ModalFooter,
15
+ Row,
16
+ TextField,
17
+ } from '@folio/stripes/components';
18
+ import css from './ModalStyle.css';
19
+ import { v4 as uuidv4 } from 'uuid';
20
+ import isValidDateFormat from './helpers/isValidDateFormat';
21
+ import stripHTML from './helpers/stripHTML';
22
+ import { useIncidents } from '../../contexts/IncidentContext';
23
+
24
+ const ModalDescribeCustomer = () => {
25
+ const {
26
+ isModalUnknownCustOpen,
27
+ closeModalUnknownCust,
28
+ setSelectedCustomers,
29
+ } = useIncidents();
30
+
31
+ const [formData, setFormData] = useState({
32
+ firstName: '',
33
+ lastName: '',
34
+ description: '',
35
+ registered: false,
36
+ id: '',
37
+ details: {
38
+ sex: '',
39
+ race: '',
40
+ height: '',
41
+ weight: '',
42
+ hair: '',
43
+ eyes: '',
44
+ dateOfBirth: '',
45
+ streetAddress: '',
46
+ city: '',
47
+ state: '',
48
+ zipcode: '',
49
+ }
50
+ });
51
+
52
+ const detailsKeysArray = [
53
+ 'sex', 'race', 'height', 'weight', 'hair', 'eyes', 'dateOfBirth', 'streetAddress', 'city', 'state', 'zipcode',
54
+ ];
55
+
56
+ const handleChange = (event) => {
57
+ const { name, value } = event.target;
58
+ if (detailsKeysArray.includes(name)) {
59
+ setFormData((prev) => ({
60
+ ...prev,
61
+ details: {
62
+ ...prev.details,
63
+ [name]: value,
64
+ }
65
+ }));
66
+ } else {
67
+ // Otherwise, update the top-level fields
68
+ setFormData((prev) => ({
69
+ ...prev,
70
+ [name]: value,
71
+ }));
72
+ }
73
+ };
74
+
75
+ const handleDescriptionChange = (content) => {
76
+ const sanitizedContent = DOMPurify.sanitize(content);
77
+ setFormData(prev => ({
78
+ ...prev,
79
+ description: sanitizedContent
80
+ }));
81
+ };
82
+
83
+ const isFormDataValid = () => {
84
+ const isDateOfBirthValid = formData.details.dateOfBirth === '' || formData.details.dateOfBirth && isValidDateFormat(formData.details.dateOfBirth);
85
+
86
+ const isEditorValid =
87
+ formData.description && stripHTML(formData.description);
88
+ return isEditorValid && isDateOfBirthValid;
89
+ };
90
+
91
+ const handleSave = () => {
92
+ setSelectedCustomers((prev) => [
93
+ ...prev,
94
+ {
95
+ id: uuidv4(),
96
+ firstName: formData.firstName.trim(),
97
+ lastName: formData.lastName.trim(),
98
+ description: formData.description.trim(),
99
+ registered: false,
100
+ details: {
101
+ sex: formData.details.sex.trim(),
102
+ race: formData.details.race.trim(),
103
+ height: formData.details.height.trim(),
104
+ weight: formData.details.weight.trim(),
105
+ hair: formData.details.hair.trim(),
106
+ eyes: formData.details.eyes.trim(),
107
+ dateOfBirth: formData.details.dateOfBirth,
108
+ streetAddress: formData.details.streetAddress.trim(),
109
+ city: formData.details.city.trim(),
110
+ state: formData.details.state.trim(),
111
+ zipcode: formData.details.zipcode.trim(),
112
+ }
113
+ },
114
+ ]);
115
+ // console.log("@ModalDescribeCustomer - handleSave, formData: ", JSON.stringify(formData, null, 2));
116
+ setFormData({
117
+ firstName: '',
118
+ lastName: '',
119
+ description: '',
120
+ registered: false,
121
+ id: '',
122
+ details: {
123
+ sex: '',
124
+ race: '',
125
+ height: '',
126
+ weight: '',
127
+ hair: '',
128
+ eyes: '',
129
+ dateOfBirth: '',
130
+ streetAddress: '',
131
+ city: '',
132
+ state: '',
133
+ zipcode: '',
134
+ }
135
+ })
136
+ closeModalUnknownCust();
137
+ };
138
+
139
+ if (!isModalUnknownCustOpen) {
140
+ return null;
141
+ };
142
+
143
+ const editorModules = {
144
+ toolbar: [
145
+ [{ 'header': [1, 2, false] }],
146
+ ['bold', 'italic', 'underline'],
147
+ ],
148
+ };
149
+
150
+ const footer = (
151
+ <ModalFooter >
152
+ <Button
153
+ onClick={handleSave}
154
+ buttonStyle="primary"
155
+ disabled={!isFormDataValid()}
156
+ marginBottom0
157
+ >
158
+ <FormattedMessage id="close-continue-button" />
159
+ </Button>
160
+ {/* <Button
161
+ onClick={closeModalUnknownCust}
162
+ marginBottom0
163
+ >
164
+ <FormattedMessage id="cancel-button" />
165
+ </Button> */}
166
+ </ModalFooter>
167
+ );
168
+
169
+ return (
170
+ <Modal
171
+ style={{
172
+ minHeight: '250px',
173
+ height: '80%', // allows modal to grow/shrink based on content
174
+ maxHeight: '300vh',
175
+ maxWidth: '300vw', // modal width responsive to viewport width
176
+ width: '70%' // modal width adjusts based on content and window size
177
+ }}
178
+ open
179
+ dismissible
180
+ closeOnBackgroundClick
181
+ label={<FormattedMessage id="modal-describe-unknown-customer-label" />}
182
+ size="large"
183
+ onClose={closeModalUnknownCust}
184
+ footer={footer}
185
+ contentClass={css.modalContent}
186
+ >
187
+ <Paneset>
188
+ <Pane
189
+ defaultWidth='fill'
190
+ >
191
+ <AccordionSet>
192
+ <Accordion
193
+ label={
194
+ <FormattedMessage id="modal-describe-unknown-customer-accordion-desc-cust-label" />
195
+ }
196
+ >
197
+ <Row>
198
+ <Col xs={3}>
199
+ <TextField
200
+ onChange={handleChange}
201
+ value={formData.firstName}
202
+ name="firstName"
203
+ label={
204
+ <FormattedMessage id="modal-describe-unknown-customer.textField-first-name-label" />
205
+ }
206
+ />
207
+ <TextField
208
+ onChange={handleChange}
209
+ value={formData.lastName}
210
+ name="lastName"
211
+ label={
212
+ <FormattedMessage id="modal-describe-unknown-customer.textField-last-name-label" />
213
+ }
214
+ />
215
+ </Col>
216
+ </Row>
217
+ <Row>
218
+ <Col xs={6}>
219
+ <Editor
220
+ required
221
+ label={
222
+ <FormattedMessage id="modal-describe-unknown-customer.textArea-description-label" />
223
+ }
224
+ value={formData.description}
225
+ onChange={handleDescriptionChange}
226
+ modules={editorModules}
227
+ />
228
+ </Col>
229
+ </Row>
230
+ </Accordion>
231
+
232
+ <Accordion
233
+ label={
234
+ <FormattedMessage id="modal-describe-unknown-customer-accordion-identity-details-label" />
235
+ }
236
+ >
237
+ <Row>
238
+ <Col xs={2}>
239
+ <TextField
240
+ label={<FormattedMessage id="modal-customer-details-sex" />}
241
+ name="sex"
242
+ value={formData.details.sex}
243
+ onChange={handleChange}
244
+ />
245
+ </Col>
246
+ <Col xs={2}>
247
+ <TextField
248
+ label={
249
+ <FormattedMessage id="modal-customer-details-race" />
250
+ }
251
+ name="race"
252
+ value={formData.details.race}
253
+ onChange={handleChange}
254
+ />
255
+ </Col>
256
+ <Col xs={2}>
257
+ <TextField
258
+ label={
259
+ <FormattedMessage id="modal-customer-details-height" />
260
+ }
261
+ name="height"
262
+ value={formData.details.height}
263
+ onChange={handleChange}
264
+ />
265
+ </Col>
266
+ <Col xs={2}>
267
+ <TextField
268
+ label={
269
+ <FormattedMessage id="modal-customer-details-weight" />
270
+ }
271
+ name="weight"
272
+ value={formData.details.weight}
273
+ onChange={handleChange}
274
+ />
275
+ </Col>
276
+ </Row>
277
+
278
+ <Row>
279
+ <Col xs={2}>
280
+ <TextField
281
+ label={
282
+ <FormattedMessage id="modal-customer-details-hair" />
283
+ }
284
+ name="hair"
285
+ value={formData.details.hair}
286
+ onChange={handleChange}
287
+ />
288
+ </Col>
289
+ <Col xs={2}>
290
+ <TextField
291
+ label={
292
+ <FormattedMessage id="modal-customer-details-eyes" />
293
+ }
294
+ name="eyes"
295
+ value={formData.details.eyes}
296
+ onChange={handleChange}
297
+ />
298
+ </Col>
299
+ <Col xs={3}>
300
+ <Datepicker
301
+ label={
302
+ <FormattedMessage id="modal-customer-details-date-of-birth" />
303
+ }
304
+ name="dateOfBirth"
305
+ value={formData.details.dateOfBirth}
306
+ onChange={handleChange}
307
+ />
308
+ </Col>
309
+ </Row>
310
+
311
+ <Row style={{ marginTop: '25px', marginBottom: '100px' }}>
312
+ <Col xs={4}>
313
+ <TextField
314
+ label={
315
+ <FormattedMessage id="modal-customer-details-street-address" />
316
+ }
317
+ name="streetAddress"
318
+ value={formData.details.streetAddress}
319
+ onChange={handleChange}
320
+ />
321
+ </Col>
322
+ <Col xs={2}>
323
+ <TextField
324
+ label={
325
+ <FormattedMessage id="modal-customer-details-city" />
326
+ }
327
+ name="city"
328
+ value={formData.details.city}
329
+ onChange={handleChange}
330
+ />
331
+ </Col>
332
+ <Col xs={2}>
333
+ <TextField
334
+ label={
335
+ <FormattedMessage id="modal-customer-details-state" />
336
+ }
337
+ name="state"
338
+ value={formData.details.state}
339
+ onChange={handleChange}
340
+ />
341
+ </Col>
342
+ <Col xs={2}>
343
+ <TextField
344
+ label={
345
+ <FormattedMessage id="modal-customer-details-zipcode" />
346
+ }
347
+ name="zipcode"
348
+ value={formData.details.zipcode}
349
+ onChange={handleChange}
350
+ />
351
+ </Col>
352
+ </Row>
353
+ </Accordion>
354
+ </AccordionSet>
355
+ </Pane>
356
+ </Paneset>
357
+ </Modal>
358
+ );
359
+ };
360
+
361
+ export default ModalDescribeCustomer;
@@ -0,0 +1,156 @@
1
+
2
+
3
+ // Mock helper functions so they don’t interfere with rendering.
4
+ jest.mock('./helpers/isValidDateFormat', () => jest.fn(() => true));
5
+ jest.mock('./helpers/stripHTML', () => jest.fn((str) => str));
6
+
7
+ // Mock DOMPurify to simply return the content.
8
+ jest.mock('dompurify', () => ({
9
+ sanitize: (content) => content,
10
+ }));
11
+
12
+ // Define a stable IncidentContext object (name begins with "mock" so Jest allows it).
13
+ const mockIncidentContext = {
14
+ isModalUnknownCustOpen: true, // default: modal open for tests
15
+ closeModalUnknownCust: jest.fn(),
16
+ setSelectedCustomers: jest.fn(),
17
+ };
18
+
19
+ // Mock the IncidentContext.
20
+ jest.mock('../../contexts/IncidentContext', () => ({
21
+ useIncidents: () => mockIncidentContext,
22
+ }));
23
+
24
+ import React from 'react';
25
+ import ReactDOM from 'react-dom';
26
+ import { act } from 'react-dom/test-utils';
27
+ import ModalDescribeCustomer from './ModalDescribeCustomer';
28
+
29
+ // --- Suppress known nonfatal warnings ---
30
+ const originalConsoleError = console.error;
31
+ beforeEach(() => {
32
+ console.error = (...args) => {
33
+ if (
34
+ typeof args[0] === 'string' &&
35
+ (
36
+ args[0].includes('ReactDOM.render is no longer supported') ||
37
+ args[0].includes('Invalid hook call') ||
38
+ args[0].includes('does not recognize the') ||
39
+ args[0].includes('Unknown event handler property') ||
40
+ args[0].includes('contentClass') ||
41
+ args[0].includes('defaultWidth')
42
+ )
43
+ ) {
44
+ return;
45
+ }
46
+ originalConsoleError(...args);
47
+ };
48
+ });
49
+ afterEach(() => {
50
+ console.error = originalConsoleError;
51
+ });
52
+
53
+ // --- Mocks for External Dependencies ---
54
+ // react‑intl: FormattedMessage simply renders its id.
55
+ jest.mock('react-intl', () => ({
56
+ FormattedMessage: (props) => <span>{props.id}</span>,
57
+ }));
58
+
59
+ // @folio/stripes/components: Minimal implementations.
60
+ jest.mock('@folio/stripes/components', () => {
61
+ const React = require('react');
62
+ return {
63
+ Button: (props) => <button {...props}>{props.children}</button>,
64
+ Col: (props) => <div {...props}>{props.children}</div>,
65
+ Modal: (props) => {
66
+ let labelId = '';
67
+ // Check if the Modal received a "label" prop that is a Formatted Message with id
68
+ if (props.label && props.label.props && props.label.props.id) {
69
+ labelId = props.label.props.id;
70
+ }
71
+ return (
72
+ <div>
73
+ {/* Render the label text so it shows up in container.textContent */}
74
+ {labelId && <h2>{labelId}</h2>}
75
+
76
+ {/* Then render the modal children */}
77
+ {props.children}
78
+ </div>
79
+ );
80
+ },
81
+ Paneset: (props) => <div {...props}>{props.children}</div>,
82
+ Pane: (props) => <div {...props}>{props.children}</div>,
83
+ ModalFooter: (props) => <div {...props}>{props.children}</div>,
84
+ Row: (props) => <div {...props}>{props.children}</div>,
85
+ TextField: (props) => {
86
+ let labelText = '';
87
+ if (props.label) {
88
+ if (typeof props.label === 'object' && props.label.props && props.label.props.id) {
89
+ labelText = props.label.props.id;
90
+ } else {
91
+ labelText = props.label;
92
+ }
93
+ }
94
+ return (
95
+ <div>
96
+ {labelText && <label>{labelText}</label>}
97
+ <input type="text" {...props} />
98
+ </div>
99
+ );
100
+ },
101
+ Datepicker: (props) => <input type="date" {...props} />,
102
+ Editor: (props) => <textarea {...props} />,
103
+ AccordionSet: (props) => <div {...props}>{props.children}</div>,
104
+ Accordion: (props) => <div {...props}>{props.children}</div>,
105
+ MessageBanner: (props) => (props.show ? <div {...props}>{props.children}</div> : null),
106
+ };
107
+ });
108
+
109
+ // Mock the CSS module.
110
+ jest.mock('./ModalStyle.css', () => ({}));
111
+
112
+ // --- Test Setup ---
113
+ let container = null;
114
+ beforeEach(() => {
115
+ container = document.createElement('div');
116
+ document.body.appendChild(container);
117
+ });
118
+ afterEach(() => {
119
+ ReactDOM.unmountComponentAtNode(container);
120
+ container.remove();
121
+ container = null;
122
+ });
123
+
124
+ // --- Test Cases ---
125
+ describe('ModalDescribeCustomer', () => {
126
+ test('returns null when modal is closed', () => {
127
+ // Set the modal flag to false.
128
+ mockIncidentContext.isModalUnknownCustOpen = false;
129
+ act(() => {
130
+ ReactDOM.render(<ModalDescribeCustomer />, container);
131
+ });
132
+ expect(container.innerHTML).toBe('');
133
+ });
134
+
135
+ test('renders modal when isModalUnknownCustOpen is true', () => {
136
+ // Ensure modal is open.
137
+ mockIncidentContext.isModalUnknownCustOpen = true;
138
+ act(() => {
139
+ ReactDOM.render(<ModalDescribeCustomer />, container);
140
+ });
141
+ const text = container.textContent;
142
+ // Expect that the modal's label appears.
143
+ expect(text).toMatch(/modal-describe-unknown-customer-label/);
144
+ });
145
+
146
+ test('renders first and last name text fields in the description accordion', () => {
147
+ mockIncidentContext.isModalUnknownCustOpen = true;
148
+ act(() => {
149
+ ReactDOM.render(<ModalDescribeCustomer />, container);
150
+ });
151
+ const text = container.textContent;
152
+ // Expect that the TextField labels for first name and last name appear.
153
+ expect(text).toMatch(/modal-describe-unknown-customer\.textField-first-name-label/);
154
+ expect(text).toMatch(/modal-describe-unknown-customer\.textField-last-name-label/);
155
+ });
156
+ });
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import {
4
+ Button,
5
+ Col,
6
+ Modal,
7
+ ModalFooter,
8
+ Row,
9
+ } from '@folio/stripes/components';
10
+ import css from './ModalStyle.css';
11
+
12
+ const ModalDirtyFormWarn = ({ handleKeepEditing, handleDismissOnDirty }) => {
13
+ const footer = (
14
+ <ModalFooter>
15
+ <Button
16
+ onClick={handleKeepEditing}
17
+ buttonStyle="primary"
18
+ >
19
+ <FormattedMessage id="keep-editing-button" />
20
+ </Button>
21
+ <Button
22
+ onClick={handleDismissOnDirty}
23
+ >
24
+ <FormattedMessage id="close-without-saving-button" />
25
+ </Button>
26
+ </ModalFooter>
27
+ );
28
+
29
+ return (
30
+ <Modal
31
+ style={{
32
+ minHeight: '150px',
33
+ height: '20%',
34
+ maxHeight: '100vh',
35
+ maxWidth: '300vw',
36
+ width: '60%'
37
+ }}
38
+ open
39
+ closeOnBackgroundClick
40
+ label={<FormattedMessage id="modal-dirty-form-warn-label" />}
41
+ size="large"
42
+ footer={footer}
43
+ contentClass={css.modalContent}
44
+ >
45
+ <Row style={{ marginTop: '25px' }}>
46
+ <Col
47
+ xs={12}
48
+ style={{
49
+ display: 'flex',
50
+ justifyContent: 'center',
51
+ alignItems: 'center',
52
+ textAlign: 'center'
53
+ }}
54
+ >
55
+ <FormattedMessage id="modal-dirty-form-warn-message" />
56
+ </Col>
57
+ </Row>
58
+ </Modal>
59
+ );
60
+ };
61
+
62
+ export default ModalDirtyFormWarn;