@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,215 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { useParams } from 'react-router-dom';
5
+ import {
6
+ AccordionSet,
7
+ Accordion,
8
+ Button,
9
+ Col,
10
+ Dropdown,
11
+ DropdownMenu,
12
+ Label,
13
+ Pane,
14
+ PaneHeader,
15
+ PaneMenu,
16
+ Row,
17
+ } from '@folio/stripes/components';
18
+ import GetIncidentCategories from './GetIncidentCategories';
19
+ import GetSingleIncidentTypeDetails from './GetSingleIncidentTypeDetails';
20
+ import ModalDeleteIncidentType from './ModalDeleteIncidentType';
21
+ import GetIncidentTypesDetails from './GetIncidentTypesDetails';
22
+ import PutIncidentType from './PutIncidentType';
23
+ import getCategoryTitleById from './helpers/getCategoryTitleById';
24
+ import { useIncidents } from '../contexts/IncidentContext';
25
+
26
+ const IncidentTypeDetailsPane = ({
27
+ handleCloseDetails,
28
+ handleShowEdit,
29
+ ...props
30
+ }) => {
31
+ const { incidentCategories } = useIncidents();
32
+ const { id } = useParams();
33
+ const [detailsData, setDetailsData] = useState(null);
34
+ const [allIncidentTypes, setAllIncidentTypes] = useState([]);
35
+ const [isClickDelete, setIsClickDelete] = useState(false);
36
+ const [toDeleteId, setToDeleteId] = useState(null);
37
+ const [formattedData, setFormattedData] = useState(null);
38
+
39
+ const handleFetchedDetails = (data) => {
40
+ setDetailsData(data);
41
+ };
42
+
43
+ const handleIncidentTypes = (data) => {
44
+ setAllIncidentTypes(data);
45
+ };
46
+
47
+ const handleShowModal = (incidentTypeId) => {
48
+ setIsClickDelete(true);
49
+ setToDeleteId(incidentTypeId);
50
+ };
51
+
52
+ const handleCloseModal = () => {
53
+ setIsClickDelete(false);
54
+ setToDeleteId(null);
55
+ };
56
+
57
+ const handleDeleteSuccess = () => {
58
+ handleCloseModal();
59
+ handleCloseDetails();
60
+ };
61
+
62
+ const handleDelete = () => {
63
+ if (toDeleteId) {
64
+ const updatedIncidentTypes = allIncidentTypes.filter((type) => {
65
+ return type.id !== toDeleteId;
66
+ });
67
+ const readyFormattedData = {
68
+ data: {
69
+ value: {
70
+ incidentTypes: updatedIncidentTypes
71
+ }
72
+ }
73
+ }
74
+ setFormattedData(readyFormattedData);
75
+ }
76
+ };
77
+
78
+ const { category_id, title, description } = detailsData || {};
79
+
80
+
81
+ const categoryTitle = React.useMemo(
82
+ () => getCategoryTitleById(incidentCategories, category_id),
83
+ [incidentCategories, category_id]
84
+ );
85
+
86
+
87
+
88
+
89
+ const style = {
90
+ display: 'block',
91
+ width: '50%',
92
+ marginTop: '10px',
93
+ };
94
+
95
+ const lastMenu = (
96
+ <PaneMenu>
97
+ <Dropdown
98
+ label={<FormattedMessage id="dropdown-actions-button" />}
99
+ buttonProps={{ buttonStyle: 'primary' }}
100
+ style={{ marginTop: '8px' }}
101
+ >
102
+ <DropdownMenu>
103
+ <Button
104
+ buttonStyle="primary"
105
+ style={style}
106
+ onClick={() => handleShowEdit(id)}
107
+ >
108
+ <FormattedMessage id="edit-button" />
109
+ </Button>
110
+ <Button
111
+ buttonStyle="warning"
112
+ style={style}
113
+ onClick={() => handleShowModal(id)}
114
+ >
115
+ <FormattedMessage id="settings.incident-types.details-delete-button" />
116
+ </Button>
117
+ </DropdownMenu>
118
+ </Dropdown>
119
+ </PaneMenu>
120
+ );
121
+
122
+ const renderHeader = (renderProps) => (
123
+ <PaneHeader
124
+ {...renderProps}
125
+ dismissible
126
+ onClose={handleCloseDetails}
127
+ paneTitle={
128
+ <FormattedMessage id="settings.incident-types.details.paneTitle" />
129
+ }
130
+ lastMenu={lastMenu}
131
+ />
132
+ );
133
+
134
+ return (
135
+ <Pane
136
+ defaultWidth="70%"
137
+ paneTitle={
138
+ <FormattedMessage id="settings.incident-types.details.paneTitle" />
139
+ }
140
+ renderHeader={renderHeader}
141
+ >
142
+ <GetIncidentCategories />
143
+ <GetSingleIncidentTypeDetails
144
+ key={id}
145
+ detailsId={id}
146
+ handleFetchedDetails={handleFetchedDetails}
147
+ />
148
+ <GetIncidentTypesDetails
149
+ context="settings"
150
+ handleIncidentTypes={handleIncidentTypes}
151
+ />
152
+ {formattedData && (
153
+ <PutIncidentType
154
+ data={formattedData}
155
+ context="details"
156
+ handleDeleteSuccess={handleDeleteSuccess}
157
+ />
158
+ )}
159
+ {isClickDelete && (
160
+ <ModalDeleteIncidentType
161
+ isOpen={isClickDelete}
162
+ onClose={handleCloseModal}
163
+ onConfirm={handleDelete}
164
+ />
165
+ )}
166
+
167
+ <AccordionSet>
168
+ <Accordion
169
+ label={
170
+ <FormattedMessage id="settings.incident-types.details.accordion-general-info-label" />
171
+ }
172
+ >
173
+ <Row>
174
+ <Col xs={8}>
175
+ <Col>
176
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
177
+ <FormattedMessage id="settings.incident-types.details.title-label" />
178
+ </Label>
179
+ <p>{title}</p>
180
+ </Col>
181
+ </Col>
182
+ </Row>
183
+ <Row>
184
+ <Col xs={8}>
185
+ <Col>
186
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
187
+ <FormattedMessage id="settings.incident-types.details.category-label" />
188
+ </Label>
189
+ <p>{getCategoryTitleById(incidentCategories, category_id)}</p>
190
+ </Col>
191
+ </Col>
192
+ </Row>
193
+ <Row>
194
+ <Col xs={10}>
195
+ <Label style={{ marginTop: '5px' }} size="medium" tag="h2">
196
+ <FormattedMessage id="settings.incident-types.details.description-label" />
197
+ </Label>
198
+ <p>{description}</p>
199
+ </Col>
200
+ </Row>
201
+ </Accordion>
202
+ <Accordion
203
+ label={<FormattedMessage id="settings.additional-accordion-label" />}
204
+ />
205
+ </AccordionSet>
206
+ </Pane>
207
+ );
208
+ };
209
+
210
+ IncidentTypeDetailsPane.propTypes = {
211
+ handleCloseDetails: PropTypes.func.isRequired,
212
+ handleShowEdit: PropTypes.func.isRequired,
213
+ };
214
+
215
+ export default IncidentTypeDetailsPane;
@@ -0,0 +1,220 @@
1
+ /**
2
+ * IncidentTypeDetailsPane.test.js
3
+ * Snapshot + smoke + basic behavior tests for the IncidentTypeDetailsPane.
4
+ */
5
+ import React from 'react';
6
+ import { act } from 'react-dom/test-utils';
7
+ import { createRoot } from 'react-dom/client';
8
+ import IncidentTypeDetailsPane from './IncidentTypeDetailsPane';
9
+
10
+ /* ------------------------------------------------------------------ *
11
+ * 1) external-library mocks
12
+ * ------------------------------------------------------------------ */
13
+ jest.mock('react-intl', () => ({
14
+ useIntl : () => ({ formatMessage: ({ id }) => id }),
15
+ FormattedMessage : (p) => <span>{p.id}</span>,
16
+ }));
17
+
18
+ jest.mock('react-router-dom', () => ({
19
+ useParams: () => ({ id: 't1' }),
20
+ }));
21
+
22
+ jest.mock('@folio/stripes/components', () => {
23
+ const React = require('react');
24
+ const mk = (tag) => (p) => React.createElement(tag, p, p.children);
25
+
26
+ // Pane: render header (so lastMenu appears) and children
27
+ const Pane = (p) => (
28
+ <div>
29
+ {typeof p.renderHeader === 'function' ? p.renderHeader({}) : p.renderHeader}
30
+ {p.children}
31
+ {p.footer}
32
+ </div>
33
+ );
34
+
35
+ // PaneHeader: include lastMenu so dropdown content is in DOM
36
+ const PaneHeader = (p) => <div>{p.children}{p.lastMenu}</div>;
37
+
38
+ // Simple dropdowns: just render children (always "open" for tests)
39
+ const Dropdown = (p) => <div>{p.children}</div>;
40
+ const DropdownMenu = (p) => <div>{p.children}</div>;
41
+
42
+ // Buttons must be clickable
43
+ const Button = (p) => <button {...p}>{p.children}</button>;
44
+
45
+ return {
46
+ Accordion : mk('div'),
47
+ AccordionSet : mk('div'),
48
+ Button,
49
+ Col : mk('div'),
50
+ Dropdown,
51
+ DropdownMenu,
52
+ Label : mk('label'),
53
+ Pane,
54
+ PaneHeader,
55
+ PaneMenu : mk('div'),
56
+ Paneset : mk('div'),
57
+ Row : mk('div'),
58
+ };
59
+ });
60
+
61
+ /* ------------------------------------------------------------------ *
62
+ * 2) child-component mocks
63
+ * ------------------------------------------------------------------ */
64
+ // Provide categories via context; this component also calls GetIncidentCategories
65
+ jest.mock('./GetIncidentCategories', () => () => (
66
+ <div>Mock GetIncidentCategories</div>
67
+ ));
68
+
69
+ // Feed details so content fields render
70
+ jest.mock('./GetSingleIncidentTypeDetails', () => {
71
+ const React = require('react');
72
+ return (props) => {
73
+ React.useEffect(() => {
74
+ props.handleFetchedDetails?.({
75
+ id: 't1',
76
+ title: 'Type 1 - Disorderly',
77
+ category_id: 'cat-1',
78
+ description: 'Initial description',
79
+ });
80
+ }, []);
81
+ return <div>Mock GetSingleIncidentTypeDetails</div>;
82
+ };
83
+ });
84
+
85
+ // Provide all types so delete logic can filter & submit
86
+ jest.mock('./GetIncidentTypesDetails', () => {
87
+ const React = require('react');
88
+ return (props) => {
89
+ React.useEffect(() => {
90
+ props.handleIncidentTypes?.([
91
+ { id: 't1', title: 'Type 1 - Disorderly', category_id: 'cat-1', description: 'Initial description' },
92
+ { id: 't2', title: 'Type 2 - Theft', category_id: 'cat-2', description: 'Other' },
93
+ ]);
94
+ }, []);
95
+ return <div>Mock GetIncidentTypesDetails</div>;
96
+ };
97
+ });
98
+
99
+ // Render a confirm/close pair to drive the deletion flow
100
+ jest.mock('./ModalDeleteIncidentType', () => (p) => (
101
+ p.isOpen ? (
102
+ <div>
103
+ <div>Mock ModalDeleteIncidentType</div>
104
+ <button onClick={p.onConfirm}>confirm-delete</button>
105
+ <button onClick={p.onClose}>close-modal</button>
106
+ </div>
107
+ ) : null
108
+ ));
109
+
110
+ // Marker for submission
111
+ jest.mock('./PutIncidentType', () => (p) => (
112
+ <div>Mock PutIncidentType (context={p?.context})</div>
113
+ ));
114
+
115
+ /* ------------------------------------------------------------------ *
116
+ * 3) IncidentContext mock
117
+ * ------------------------------------------------------------------ */
118
+ jest.mock('../contexts/IncidentContext', () => ({
119
+ useIncidents: () => ({
120
+ incidentCategories: [
121
+ { id: 'cat-1', title: 'Behavior' },
122
+ { id: 'cat-2', title: 'Property' },
123
+ ],
124
+ }),
125
+ }));
126
+
127
+ /* ------------------------------------------------------------------ *
128
+ * 4) DOM setup / teardown
129
+ * ------------------------------------------------------------------ */
130
+ let container, root;
131
+ beforeEach(() => {
132
+ container = document.createElement('div');
133
+ document.body.appendChild(container);
134
+ root = createRoot(container);
135
+ });
136
+ afterEach(() => {
137
+ root.unmount();
138
+ document.body.removeChild(container);
139
+ container = null;
140
+ });
141
+
142
+ /* ------------------------------------------------------------------ *
143
+ * 5) helpers
144
+ * ------------------------------------------------------------------ */
145
+ const findButtonByText = (rootEl, text) =>
146
+ Array.from(rootEl.querySelectorAll('button')).find(b => (b.textContent || '').includes(text));
147
+
148
+ /* ------------------------------------------------------------------ *
149
+ * 6) tests
150
+ * ------------------------------------------------------------------ */
151
+ it('renders without crashing (snapshot)', async () => {
152
+ await act(async () => {
153
+ root.render(
154
+ <IncidentTypeDetailsPane handleCloseDetails={jest.fn()} handleShowEdit={jest.fn()} />
155
+ );
156
+ });
157
+ await act(async () => {}); // flush effects
158
+ expect(container.innerHTML).toMatchSnapshot();
159
+ });
160
+
161
+ it('mounts key child components', async () => {
162
+ await act(async () => {
163
+ root.render(
164
+ <IncidentTypeDetailsPane handleCloseDetails={jest.fn()} handleShowEdit={jest.fn()} />
165
+ );
166
+ });
167
+ await act(async () => {});
168
+ const txt = container.textContent;
169
+ expect(txt).toContain('Mock GetIncidentCategories');
170
+ expect(txt).toContain('Mock GetSingleIncidentTypeDetails');
171
+ expect(txt).toContain('Mock GetIncidentTypesDetails');
172
+ });
173
+
174
+ it('renders details fields (title, category, description)', async () => {
175
+ await act(async () => {
176
+ root.render(
177
+ <IncidentTypeDetailsPane handleCloseDetails={jest.fn()} handleShowEdit={jest.fn()} />
178
+ );
179
+ });
180
+ await act(async () => {});
181
+ const txt = container.textContent;
182
+ expect(txt).toContain('Type 1 - Disorderly'); // title
183
+ expect(txt).toContain('Behavior'); // category (resolved by helper against context)
184
+ expect(txt).toContain('Initial description'); // description
185
+ });
186
+
187
+ it('invokes handleShowEdit when Edit is clicked', async () => {
188
+ const onEdit = jest.fn();
189
+ await act(async () => {
190
+ root.render(
191
+ <IncidentTypeDetailsPane handleCloseDetails={jest.fn()} handleShowEdit={onEdit} />
192
+ );
193
+ });
194
+ await act(async () => {});
195
+ const editBtn = findButtonByText(container, 'edit-button');
196
+ expect(editBtn).toBeTruthy();
197
+ await act(async () => { editBtn.click(); });
198
+ expect(onEdit).toHaveBeenCalledWith('t1');
199
+ });
200
+
201
+ it('opens delete modal and submits PutIncidentType on confirm', async () => {
202
+ await act(async () => {
203
+ root.render(
204
+ <IncidentTypeDetailsPane handleCloseDetails={jest.fn()} handleShowEdit={jest.fn()} />
205
+ );
206
+ });
207
+ await act(async () => {});
208
+ // Click the delete action in the dropdown
209
+ const deleteBtn = findButtonByText(container, 'settings.incident-types.details-delete-button');
210
+ expect(deleteBtn).toBeTruthy();
211
+ await act(async () => { deleteBtn.click(); });
212
+
213
+ // Modal should be visible; confirm deletion
214
+ const confirm = findButtonByText(container, 'confirm-delete');
215
+ expect(confirm).toBeTruthy();
216
+ await act(async () => { confirm.click(); });
217
+
218
+ // Submission marker appears
219
+ expect(container.textContent).toContain('Mock PutIncidentType (context=details)');
220
+ });
@@ -0,0 +1,211 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { useParams } from 'react-router-dom';
5
+ import {
6
+ Accordion,
7
+ AccordionSet,
8
+ Button,
9
+ Col,
10
+ Pane,
11
+ PaneHeader,
12
+ PaneFooter,
13
+ Row,
14
+ Select,
15
+ TextArea,
16
+ TextField,
17
+ } from '@folio/stripes/components';
18
+ import GetSingleIncidentTypeDetails from './GetSingleIncidentTypeDetails';
19
+ import GetIncidentTypesDetails from './GetIncidentTypesDetails';
20
+ import GetIncidentCategories from './GetIncidentCategories';
21
+ import PutIncidentType from './PutIncidentType';
22
+ import { useIncidents } from '../contexts/IncidentContext';
23
+
24
+ const IncidentTypeEditPane = ({ handleCloseEdit, ...props }) => {
25
+
26
+ const { incidentCategories } = useIncidents();
27
+ const { id } = useParams();
28
+ const [detailsData, setDetailsData] = useState(null);
29
+ // const [incidentCategories, setIncidentCategories] = useState([]);
30
+ const [allIncidentTypes, setAllIncidentTypes] = useState([]);
31
+ const [formattedData, setFormattedData] = useState(null);
32
+ const [formData, setFormData] = useState({
33
+ title: '',
34
+ category_id: '',
35
+ description: '',
36
+ });
37
+
38
+ const handleFetchedDetails = (data) => {
39
+ setDetailsData(data);
40
+ };
41
+
42
+ // const handleFetchedCategories = (data) => {
43
+ // setIncidentCategories(data);
44
+ // };
45
+
46
+ const handleIncidentTypes = (data) => {
47
+ setAllIncidentTypes(data);
48
+ };
49
+
50
+ const dataOptions = incidentCategories.map((category) => ({
51
+ value: category.id,
52
+ label: category.title,
53
+ }));
54
+
55
+ useEffect(() => {
56
+ if (detailsData) {
57
+ setFormData({
58
+ title: detailsData.title || '',
59
+ category_id: detailsData.category_id || '',
60
+ description: detailsData.description || '',
61
+ });
62
+ }
63
+ }, [detailsData]);
64
+
65
+ const handleChange = (e) => {
66
+ const { name, value } = e.target;
67
+ setFormData((prev) => ({
68
+ ...prev,
69
+ [name]: value,
70
+ }));
71
+ };
72
+
73
+ const isFormDataPresent = () => {
74
+ return formData.title && formData.category_id && formData.description;
75
+ };
76
+
77
+ const handleSubmit = (e) => {
78
+ if (e) e.preventDefault();
79
+ console.log("formData: ", JSON.stringify(formData, null, 2))
80
+ const updatedIncidentTypes = allIncidentTypes.map((type) => {
81
+ if (type.id === id) {
82
+ return {
83
+ ...type,
84
+ ...formData
85
+ };
86
+ } else {
87
+ return type;
88
+ }
89
+ });
90
+ const formattedReadyData = {
91
+ data: {
92
+ value: {
93
+ incidentTypes: updatedIncidentTypes
94
+ }
95
+ }
96
+ };
97
+ setFormattedData(formattedReadyData);
98
+ };
99
+
100
+ const renderHeader = (renderProps) => (
101
+ <PaneHeader
102
+ {...renderProps}
103
+ dismissible
104
+ paneTitle={
105
+ <FormattedMessage id="settings.incident-types-edit.paneTitle" />
106
+ }
107
+ onClose={handleCloseEdit}
108
+ />
109
+ );
110
+
111
+ const footer = (
112
+ <PaneFooter
113
+ renderStart={
114
+ <Button onClick={handleCloseEdit}>
115
+ <FormattedMessage id="cancel-button" />
116
+ </Button>
117
+ }
118
+ renderEnd={
119
+ <Button
120
+ buttonStyle="primary"
121
+ onClick={handleSubmit}
122
+ disabled={!isFormDataPresent()}
123
+ >
124
+ <FormattedMessage id="save-and-close-button" />
125
+ </Button>
126
+ }
127
+ />
128
+ );
129
+
130
+ return (
131
+ <Pane defaultWidth="100%" renderHeader={renderHeader} footer={footer}>
132
+ <GetSingleIncidentTypeDetails
133
+ detailsId={id}
134
+ handleFetchedDetails={handleFetchedDetails}
135
+ />
136
+ <GetIncidentCategories />
137
+ <GetIncidentTypesDetails
138
+ context="settings"
139
+ handleIncidentTypes={handleIncidentTypes}
140
+ />
141
+ {formattedData && (
142
+ <PutIncidentType
143
+ data={formattedData}
144
+ context="edit"
145
+ handleCloseEdit={handleCloseEdit}
146
+ />
147
+ )}
148
+
149
+ <AccordionSet>
150
+ <Accordion
151
+ label={
152
+ <FormattedMessage id="settings.incident-types-edit.type-info-accordion-label" />
153
+ }
154
+ >
155
+ <Row>
156
+ <Col xs={8}>
157
+ <Col>
158
+ <TextField
159
+ required={true}
160
+ label={
161
+ <FormattedMessage id="settings.incident-types-edit.title-text-field-label" />
162
+ }
163
+ name="title"
164
+ value={formData.title}
165
+ onChange={handleChange}
166
+ />
167
+ </Col>
168
+ </Col>
169
+ </Row>
170
+ <Row>
171
+ <Col xs={4}>
172
+ <Col>
173
+ <Select
174
+ required={true}
175
+ label={
176
+ <FormattedMessage id="settings.incident-types-edit.category-select-label" />
177
+ }
178
+ name="category_id"
179
+ value={formData.category_id}
180
+ placeholder="Select a category"
181
+ dataOptions={dataOptions}
182
+ onChange={handleChange}
183
+ />
184
+ </Col>
185
+ </Col>
186
+ </Row>
187
+ <Row>
188
+ <Col xs={6}>
189
+ <TextArea
190
+ required={true}
191
+ label={
192
+ <FormattedMessage id="settings.incident-types-edit.description-text-area-label" />
193
+ }
194
+ name="description"
195
+ value={formData.description}
196
+ onChange={handleChange}
197
+ style={{ width: '100%', height: '200px' }}
198
+ />
199
+ </Col>
200
+ </Row>
201
+ </Accordion>
202
+ </AccordionSet>
203
+ </Pane>
204
+ );
205
+ };
206
+
207
+ IncidentTypeEditPane.propTypes = {
208
+ handleCloseEdit: PropTypes.func.isRequired,
209
+ };
210
+
211
+ export default IncidentTypeEditPane;