@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,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;
|