@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,171 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import { useParams } from 'react-router-dom';
4
+ import {
5
+ AccordionSet,
6
+ Accordion,
7
+ Button,
8
+ Col,
9
+ Dropdown,
10
+ DropdownMenu,
11
+ Headline,
12
+ KeyValue,
13
+ Label,
14
+ Pane,
15
+ PaneMenu,
16
+ Row,
17
+ } from '@folio/stripes/components';
18
+ import GetTrespassReasons from './GetTrespassReasons';
19
+ import { useIncidents } from '../contexts/IncidentContext';
20
+ import PutTrespassReasons from './PutTrespassReasons';
21
+ import ModalDeleteTrespassReason from './ModalDeleteTrespassReason';
22
+
23
+ const TrespassReasonDetailsPane = ({handleShowEdit, handleCloseDetails, ...props}, ) => {
24
+ const { id } = useParams();
25
+ const {
26
+ trespassReasons
27
+ } = useIncidents();
28
+
29
+ const [isClickDelete, setIsClickDelete] = useState(false);
30
+ const [toDeleteId, setToDeleteId] = useState(null);
31
+ const [formattedData, setFormattedData] = useState(null);
32
+ const [viewTrespassReason, setViewTrespassReason] = useState({
33
+ reason: '',
34
+ isDefault: false,
35
+ isSuppressed: false
36
+ });
37
+
38
+ useEffect(() => {
39
+ const singleTRbyId = trespassReasons.find(tr => tr.id === id);
40
+ // console.log("singleTRbyId --> ", JSON.stringify(singleTRbyId, null, 2))
41
+ if (singleTRbyId) {
42
+ setViewTrespassReason(singleTRbyId)
43
+ }
44
+ }, [id, trespassReasons])
45
+
46
+ const handleShowModal = (incidentTypeId) => {
47
+ setIsClickDelete(true);
48
+ setToDeleteId(incidentTypeId);
49
+ };
50
+
51
+ const handleCloseModal = () => {
52
+ setIsClickDelete(false);
53
+ setToDeleteId(null);
54
+ };
55
+
56
+ const handleDeleteSuccess = () => {
57
+ handleCloseModal();
58
+ handleCloseDetails();
59
+ };
60
+
61
+ const handleDelete = () => {
62
+ if (toDeleteId) {
63
+ const updatedTrespassReasons = trespassReasons.filter((tr) => {
64
+ return tr.id !== toDeleteId;
65
+ });
66
+ const readyFormattedData = {
67
+ data: {
68
+ value: {
69
+ trespassReasons: updatedTrespassReasons
70
+ }
71
+ }
72
+ }
73
+ setFormattedData(readyFormattedData);
74
+ }
75
+ };
76
+
77
+ const lastMenu = (
78
+ <PaneMenu>
79
+ <Dropdown
80
+ label={<FormattedMessage id="dropdown-actions-button" />}
81
+ buttonProps={{ buttonStyle: 'primary' }}
82
+ style={{ marginTop: '8px' }}
83
+ >
84
+ <DropdownMenu>
85
+ <Button
86
+ style={{ marginTop: '10px' }}
87
+ buttonStyle="primary"
88
+ onClick={() => handleShowEdit(id)}
89
+ >
90
+ <FormattedMessage id="edit-button" />
91
+ </Button>
92
+ <Button
93
+ style={{ marginTop: '10px' }}
94
+ buttonStyle="warning"
95
+ onClick={() => handleShowModal(id)}
96
+ >
97
+ <FormattedMessage id="settings.incident-types.details-delete-button" />
98
+ </Button>
99
+ </DropdownMenu>
100
+ </Dropdown>
101
+ </PaneMenu>
102
+ );
103
+
104
+ return (
105
+ <Pane
106
+ dismissible
107
+ onClose={handleCloseDetails}
108
+ defaultWidth="100%"
109
+ paneTitle={<FormattedMessage id="settings.trespass-reason-paneTitle"/>}
110
+ {...props}
111
+ lastMenu={lastMenu}>
112
+
113
+ <GetTrespassReasons />
114
+
115
+ {isClickDelete && (
116
+ <ModalDeleteTrespassReason
117
+ isOpen={isClickDelete}
118
+ onClose={handleCloseModal}
119
+ onConfirm={handleDelete}
120
+ />
121
+ )}
122
+
123
+ {formattedData && (
124
+ <PutTrespassReasons
125
+ data={formattedData}
126
+ context='delete'
127
+ handleDeleteSuccess={handleDeleteSuccess}
128
+ />
129
+ )}
130
+
131
+ <AccordionSet>
132
+ <Accordion label='Details'>
133
+ <Row>
134
+ <Col xs={6}>
135
+ <KeyValue
136
+ label={<FormattedMessage id="settings.trespass-reason-details-reason-label"/>}
137
+ value={viewTrespassReason.reason || ''}
138
+ />
139
+ </Col>
140
+ </Row>
141
+
142
+ <Row>
143
+ <Col xs={6}>
144
+ <KeyValue
145
+ label={<FormattedMessage id="settings.trespass-reason-details-default-label"/>}
146
+ value={viewTrespassReason.isDefault ?
147
+ (<FormattedMessage id="settings.trespass-reason-details-yes"/>)
148
+ : (<FormattedMessage id="settings.trespass-reason-details-no"/>)
149
+ }
150
+ />
151
+ </Col>
152
+ </Row>
153
+
154
+ <Row>
155
+ <Col xs={6}>
156
+ <KeyValue
157
+ label={ <FormattedMessage id="settings.trespass-reason-details-isSuppressed-label"/>}
158
+ value={viewTrespassReason.isSuppressed ?
159
+ (<FormattedMessage id="settings.trespass-reason-details-yes"/>)
160
+ : (<FormattedMessage id="settings.trespass-reason-details-no"/>)
161
+ }
162
+ />
163
+ </Col>
164
+ </Row>
165
+ </Accordion>
166
+ </AccordionSet>
167
+ </Pane>
168
+ );
169
+ };
170
+
171
+ export default TrespassReasonDetailsPane;
@@ -0,0 +1,221 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import { useParams } from 'react-router-dom';
4
+ import {
5
+ AccordionSet,
6
+ Accordion,
7
+ Button,
8
+ Checkbox,
9
+ Col,
10
+ LoadingPane,
11
+ Pane,
12
+ Row,
13
+ TextArea
14
+ } from '@folio/stripes/components';
15
+ import GetTrespassReasons from './GetTrespassReasons';
16
+ import PutTrespassReasons from './PutTrespassReasons';
17
+ import { useIncidents } from '../contexts/IncidentContext';
18
+
19
+ const TrespassReasonEditPane = ({handleCancelEdit, handleCloseEdit, ...props}) => {
20
+ const { id } = useParams();
21
+ const {
22
+ trespassReasons
23
+ } = useIncidents();
24
+
25
+ const [formattedTrespassReasons, setFormattedTrespassReasons] = useState(null);
26
+ const [isButtonDisabled, setIsButtonDisabled] = useState(false);
27
+ const [trIsLoading, setTrIsLoading] = useState(false);
28
+ const [formData, setFormData] = useState({
29
+ id: '',
30
+ reason: '',
31
+ isDefault: false,
32
+ isSuppressed: false
33
+ });
34
+
35
+ // useEffect(() => {
36
+ // console.log("formData --> ", JSON.stringify(formData, null, 2))
37
+ // }, [formData]);
38
+
39
+ useEffect(() => {
40
+ if (trespassReasons && trespassReasons.length > 0) {
41
+ const foundTR = trespassReasons.find((tr) => tr.id === id);
42
+ if (foundTR) {
43
+ setFormData({
44
+ id: foundTR.id,
45
+ reason: foundTR.reason || '',
46
+ ...(foundTR.isDefault ? {isDefault: foundTR.isDefault} : {}),
47
+ ...(foundTR.isSuppressed ? {isSuppressed: foundTR.isSuppressed} : {})
48
+ });
49
+ } else {
50
+ console.error(`trespass reason with id ${id} not found`);
51
+ setFormData({
52
+ id: '',
53
+ reason: '',
54
+ });
55
+ };
56
+ };
57
+ }, [id, trespassReasons]);
58
+
59
+ const handleChange = (event) => {
60
+ const { name, value } = event.target
61
+ if (name === 'isDefault') {
62
+ setFormData((prev) => ({
63
+ ...prev,
64
+ [name]: event.target.checked
65
+ }));
66
+ } else if (name === 'isSuppressed') {
67
+ setFormData((prev) => ({
68
+ ...prev,
69
+ [name]: event.target.checked
70
+ }))
71
+ } else {
72
+ setFormData((prev) => ({
73
+ ...prev,
74
+ [name]: value
75
+ }));
76
+ };
77
+ };
78
+
79
+ const handleFinishEdit = () => {
80
+ setTrIsLoading(false);
81
+ handleCloseEdit(id);
82
+ };
83
+
84
+ const stripFalseFlags = (tr, keys = ['isDefault', 'isSuppressed']) => {
85
+ const out = { ...tr };
86
+ for (const k of keys) {
87
+ if (out[k] === false) delete out[k];
88
+ else if (out[k] === true) out[k] = true; // normalize to literal true
89
+ }
90
+ return out;
91
+ };
92
+
93
+ const handleEditSaveAndClose = () => {
94
+ const newTR = {
95
+ id: formData.id,
96
+ reason: formData.reason,
97
+ isSuppressed: !!formData.isSuppressed,
98
+ isDefault: formData.isSuppressed ? false : !!formData.isDefault,
99
+ };
100
+
101
+ const upsertById = (list, item) => {
102
+ let found = false;
103
+ const replaced = list.map(tr => {
104
+ if (tr.id === item.id) {
105
+ found = true;
106
+ return { ...tr, ...item };
107
+ }
108
+ return tr;
109
+ });
110
+ return found ? replaced : [...replaced, item];
111
+ };
112
+
113
+ let next = upsertById(trespassReasons, newTR);
114
+
115
+ // no suppressed item can be default
116
+ next = next.map(tr => (tr.isSuppressed ? { ...tr, isDefault: false} : tr));
117
+
118
+ // ensure single isDefault (having a default is not required)
119
+ if (newTR.isDefault) {
120
+ next = next.map(tr => tr.id === newTR.id ? tr : { ...tr, isDefault: false }
121
+ );
122
+ };
123
+
124
+ // remove false flags (no persist isDefault: false, isSuppressed: false, only persist if true)
125
+ next = next.map(tr => stripFalseFlags(tr, ['isDefault', 'isSuppressed']));
126
+
127
+ const readyFormattedData = {
128
+ value: { trespassReasons: next }
129
+ };
130
+
131
+ // console.log("ON SAVE, pre data key -> ", JSON.stringify(readyFormattedData, null, 2))
132
+
133
+ // triggers <PutTrespassReasons />
134
+ setFormattedTrespassReasons({ data: readyFormattedData })
135
+ };
136
+
137
+ useEffect(() => {
138
+ const isReasonNotEmpty = formData.reason && formData.reason.trim() !== '';
139
+ const isValid = isReasonNotEmpty;
140
+ setIsButtonDisabled(!isValid);
141
+ }, [formData]);
142
+
143
+ return (
144
+ <>
145
+ <GetTrespassReasons />
146
+
147
+ {formattedTrespassReasons &&
148
+ <PutTrespassReasons
149
+ data={formattedTrespassReasons}
150
+ handleFinishEdit={handleFinishEdit}
151
+ />}
152
+
153
+ {trIsLoading ? (
154
+ <LoadingPane defaultWidth='100%'/>
155
+ ) : (
156
+ <Pane
157
+ dismissible
158
+ onClose={() => handleCancelEdit(id)}
159
+ defaultWidth="100%"
160
+ paneTitle={<FormattedMessage
161
+ id="settings.trespass-reasons-paneTitle"/>
162
+ }
163
+ {...props}>
164
+
165
+ <AccordionSet>
166
+ <Accordion label={<FormattedMessage
167
+ id="details-pane.accordion-label.details"/>}
168
+ >
169
+ <Row>
170
+ <Col xs={8}>
171
+ <TextArea
172
+ required
173
+ label={<FormattedMessage id="settings.trespass-document-edit-textfield-reason-label"/>}
174
+ name='reason'
175
+ value={formData.reason}
176
+ onChange={handleChange}
177
+ />
178
+ </Col>
179
+ </Row>
180
+ <Row>
181
+ <Col xs={6}>
182
+ <Checkbox
183
+ label={<FormattedMessage id="settings.trespass-document-edit-checkbox-default-label"/>}
184
+ name='isDefault'
185
+ checked={formData.isDefault}
186
+ onChange={handleChange}
187
+ />
188
+ </Col>
189
+ </Row>
190
+ <Row>
191
+ <Col xs={6}>
192
+ <Checkbox
193
+ label={<FormattedMessage id="settings.trespass-document-edit-checkbox-suppress-label"/>}
194
+ name='isSuppressed'
195
+ checked={formData.isSuppressed}
196
+ onChange={handleChange}
197
+ />
198
+ </Col>
199
+ </Row>
200
+ <Row style={{ marginTop: '20px', marginBottom: '25px'}}>
201
+ <Col xs={4}>
202
+ <Button
203
+ buttonStyle='primary'
204
+ disabled={isButtonDisabled}
205
+ onClick={handleEditSaveAndClose}
206
+ >
207
+ <FormattedMessage
208
+ id="save-and-close-button"
209
+ />
210
+ </Button>
211
+ </Col>
212
+ </Row>
213
+ </Accordion>
214
+ </AccordionSet>
215
+ </Pane>
216
+ )}
217
+ </>
218
+ );
219
+ };
220
+
221
+ export default TrespassReasonEditPane;
@@ -0,0 +1,282 @@
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { useHistory, Switch, Route, useLocation } from 'react-router-dom';
5
+ import {
6
+ Button,
7
+ Checkbox,
8
+ Col,
9
+ Icon,
10
+ MultiColumnList,
11
+ Pane,
12
+ Paneset,
13
+ Row,
14
+ TextArea
15
+ } from '@folio/stripes/components';
16
+ import GetTrespassReasons from './GetTrespassReasons';
17
+ import PutTrespassReasons from './PutTrespassReasons';
18
+ import TrespassReasonDetailsPane from './TrespassReasonDetailsPane';
19
+ import TrespassReasonEditPane from './TrespassReasonEditPane';
20
+ import { v4 as uuidv4 } from 'uuid';
21
+ import { useIncidents } from '../contexts/IncidentContext';
22
+
23
+ const TrespassReasonsPaneset = ({...props}) => {
24
+ const history = useHistory();
25
+ const { pathname } = useLocation();
26
+ const {
27
+ trespassReasons
28
+ } = useIncidents();
29
+
30
+ const [trespassReasonsList, setTrespassReasonsList] = useState([]);
31
+ const listRoute = '/settings/incidents/trespass-reasons';
32
+ const [reloadKey, setReloadKey] = useState(0);
33
+ useEffect(() => {
34
+ if (pathname === listRoute) {
35
+ setReloadKey(key => key + 1) // guarantee fresh GET
36
+ }
37
+ }, [pathname]);
38
+ const [isButtonDisabled, setIsButtonDisabled] = useState(false);
39
+ const [formattedTrespassReasons, setFormattedTrespassReasons] = useState(null);
40
+ const [formData, setFormData] = useState({
41
+ id: '',
42
+ reason: ''
43
+ });
44
+
45
+ const sortTRlist = (trConfigList = []) => {
46
+ const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
47
+ return [...trConfigList].sort((a, b) => {
48
+ const aStr = (a?.reason ?? '').trim();
49
+ const bStr = (b?.reason ?? '').trim();
50
+ return collator.compare(aStr, bStr);
51
+ });
52
+ };
53
+
54
+ useEffect(() => {
55
+ const sortedTRlist = sortTRlist(trespassReasons);
56
+ // console.log("sortedTRlist --> ", JSON.stringify(sortedTRlist, null, 2))
57
+ setTrespassReasonsList(sortedTRlist)
58
+ }, [trespassReasons])
59
+
60
+ const handleChange = (event) => {
61
+ const { name, value } = event.target
62
+ if (name === 'isDefault') {
63
+ setFormData((prev) => ({
64
+ ...prev,
65
+ [name]: event.target.checked
66
+ }));
67
+ } else {
68
+ setFormData((prev) => ({
69
+ ...prev,
70
+ [name]: value
71
+ }));
72
+ };
73
+ };
74
+
75
+ const resetForFreshFormInput = () => {
76
+ setFormData({
77
+ id: '',
78
+ reason: ''
79
+ })
80
+ };
81
+
82
+ const makeTrespassReasonObject = () => {
83
+ return {
84
+ id: uuidv4(),
85
+ reason: formData.reason,
86
+ ...(formData.isDefault ? { isDefault: true } : {})
87
+ }
88
+ };
89
+
90
+ const handleSaveAndClose = () => {
91
+ const newTrespassReason = makeTrespassReasonObject();
92
+ let updatedTrespassReasonsList = [];
93
+
94
+ // handle can only be one TR w/ isDefault: true
95
+ if (newTrespassReason.isDefault) {
96
+ const removedPrevDefaultList = trespassReasons.map((tr) => {
97
+ const { isDefault, ...otherKeys } = tr;
98
+ return otherKeys;
99
+ });
100
+ updatedTrespassReasonsList = [...removedPrevDefaultList, newTrespassReason];
101
+ } else {
102
+ updatedTrespassReasonsList = [...trespassReasons, newTrespassReason]
103
+ };
104
+
105
+ const readyFormattedData = {
106
+ value: {
107
+ trespassReasons: updatedTrespassReasonsList
108
+ }
109
+ };
110
+
111
+ setFormattedTrespassReasons({ data: readyFormattedData })
112
+ resetForFreshFormInput();
113
+ };
114
+
115
+ const handleCloseDetails = () => {
116
+ history.push(`/settings/incidents/trespass-reasons`);
117
+ };
118
+
119
+ const handleShowDetails = (event, row) => {
120
+ const id = row.id
121
+ history.push(`/settings/incidents/trespass-reasons/${id}`);
122
+ };
123
+
124
+ const handleShowEdit = (reasonsId) => {
125
+ history.push(`/settings/incidents/trespass-reasons/${reasonsId}/edit`);
126
+ };
127
+
128
+ const handleCancelEdit = (reasonsId) => {
129
+ history.push(`/settings/incidents/trespass-reasons/${reasonsId}`);
130
+ };
131
+
132
+ const handleCloseEdit = (reasonsId) => {
133
+ history.push(`/settings/incidents/trespass-reasons/${reasonsId}`);
134
+ };
135
+
136
+ const getValidationResults = () => {
137
+ const isReasonNotEmpty = formData.reason && formData.reason.trim() !== '';
138
+ return isReasonNotEmpty
139
+ };
140
+
141
+ useEffect(() => {
142
+ setIsButtonDisabled(!getValidationResults())
143
+ }, [formData]);
144
+
145
+ const columnWidths = {
146
+ reason: '275px',
147
+ isDefault: '80px'
148
+ };
149
+
150
+ const formatter = {
151
+ reason: (item) => {
152
+ if (item.reason?.length > 48) {
153
+ return item.reason.slice(0, 72) + "...";
154
+ } else {
155
+ return item.reason;
156
+ }
157
+ },
158
+ isDefault: (item) => {
159
+ const isDefault = item.isDefault;
160
+ return isDefault ? <span style={{ color: 'green' }}>
161
+ <Icon icon='check-circle'></Icon>
162
+ </span>
163
+ : null
164
+ },
165
+ isSuppressed: (item) => {
166
+ const isSuppressed = item.isSuppressed;
167
+ return isSuppressed ? <span>
168
+ <Icon
169
+ size='small'
170
+ icon='exclamation-circle'
171
+ status='warn'>
172
+ </Icon>
173
+ </span>
174
+ : null
175
+ }
176
+ };
177
+
178
+ return (
179
+ <Paneset>
180
+ <Pane
181
+ defaultWidth="fill"
182
+ paneTitle={<FormattedMessage
183
+ id="settings.trespass-reasons-paneTitle"/>
184
+ }
185
+ >
186
+ <GetTrespassReasons key={reloadKey} reloadKey={reloadKey}/>
187
+
188
+ {formattedTrespassReasons &&
189
+ <PutTrespassReasons
190
+ data={formattedTrespassReasons}
191
+ onSuccess={() => {
192
+ setFormattedTrespassReasons(null); // unmount this component
193
+ setReloadKey(key => key + 1); // force fresh GET
194
+ }}
195
+ />}
196
+
197
+ <Row>
198
+ <Col xs={12}>
199
+ <MultiColumnList
200
+ contentData={trespassReasonsList ?? []}
201
+ visibleColumns={[
202
+ 'reason',
203
+ 'isDefault',
204
+ 'isSuppressed'
205
+ ]}
206
+ columnMapping={{
207
+ reason: <FormattedMessage id="settings.trespass-reasons.column-mapping-reason"/>,
208
+ isDefault: <FormattedMessage id="settings.trespass-reasons.column-mapping-default"/>,
209
+ isSuppressed: <FormattedMessage id="settings.trespass-reasons.column-mapping-isSuppressed"/>
210
+ }}
211
+ columnWidths={columnWidths}
212
+ formatter={formatter}
213
+ onRowClick={handleShowDetails}
214
+ />
215
+ </Col>
216
+ </Row>
217
+
218
+ <Row style={{ marginTop: '45px' }}>
219
+ <Col xs={8}>
220
+ <TextArea
221
+ required
222
+ label={<FormattedMessage id="settings.trespass-reason-textarea-reason"/>}
223
+ name='reason'
224
+ value={formData.reason}
225
+ onChange={handleChange}
226
+ />
227
+ </Col>
228
+ </Row>
229
+
230
+ <Row>
231
+ <Col xs={6}>
232
+ <Checkbox
233
+ label={<FormattedMessage id="settings.trespass-reason-checkbox-isDefault"/>}
234
+ name='isDefault'
235
+ checked={formData.isDefault}
236
+ onChange={handleChange}
237
+ />
238
+ </Col>
239
+ </Row>
240
+
241
+ <Row style={{ marginTop: '20px', marginBottom: '25px'}}>
242
+ <Col xs={4}>
243
+ <Button
244
+ buttonStyle='primary'
245
+ disabled={isButtonDisabled}
246
+ onClick={handleSaveAndClose}
247
+ >
248
+ <FormattedMessage
249
+ id="save-and-close-button"
250
+ />
251
+ </Button>
252
+ </Col>
253
+ </Row>
254
+ </Pane>
255
+
256
+ <Switch>
257
+ <Route
258
+ exact
259
+ path="/settings/incidents/trespass-reasons/:id"
260
+ render={(props) => (
261
+ <TrespassReasonDetailsPane
262
+ {...props}
263
+ handleCloseDetails={handleCloseDetails}
264
+ handleShowEdit={handleShowEdit}
265
+ />
266
+ )}/>
267
+ <Route
268
+ exact
269
+ path="/settings/incidents/trespass-reasons/:id/edit"
270
+ render={(props) => (
271
+ <TrespassReasonEditPane
272
+ handleCancelEdit={handleCancelEdit}
273
+ handleCloseEdit={handleCloseEdit}
274
+ {...props}
275
+ />
276
+ )}/>
277
+ </Switch>
278
+ </Paneset>
279
+ )
280
+ };
281
+
282
+ export default TrespassReasonsPaneset;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`renders without crashing (snapshot) 1`] = `"<div><div></div><div><div xs="10"><button><span>settings.categories-new-button</span></button></div></div><ul><li><div data-col="title">Behavior</div><div data-col="id"><div><div><button><span>edit-button</span></button><button><span>settings.categories-delete-button</span></button></div></div></div></li><li><div data-col="title">Property</div><div data-col="id"><div><div><button><span>edit-button</span></button><button><span>settings.categories-delete-button</span></button></div></div></div></li></ul><div>Mock GetIncidentCategories</div></div>"`;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`renders without crashing (snapshot) 1`] = `"<div><div><div><div><div><button buttonstyle="primary" style="display: block; width: 50%; margin-top: 10px;"><span>edit-button</span></button><button buttonstyle="warning" style="display: block; width: 50%; margin-top: 10px;"><span>settings.incident-types.details-delete-button</span></button></div></div></div></div><div>Mock GetIncidentCategories</div><div>Mock GetSingleIncidentTypeDetails</div><div>Mock GetIncidentTypesDetails</div><div><div label="[object Object]"><div><div xs="8"><div><label style="margin-top: 5px;" tag="h2"><span>settings.incident-types.details.title-label</span></label><p>Type 1 - Disorderly</p></div></div></div><div><div xs="8"><div><label style="margin-top: 5px;" tag="h2"><span>settings.incident-types.details.category-label</span></label><p>Behavior</p></div></div></div><div><div xs="10"><label style="margin-top: 5px;" tag="h2"><span>settings.incident-types.details.description-label</span></label><p>Initial description</p></div></div></div><div label="[object Object]"></div></div></div>"`;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`renders without crashing (snapshot) 1`] = `"<div><div panetitle="[object Object]"></div><div>Mock GetSingleIncidentTypeDetails</div><div>Mock GetIncidentCategories</div><div>Mock GetIncidentTypesDetails</div><div><div label="[object Object]"><div><div xs="8"><div><input required="" label="[object Object]" name="title" value="Type 1 - Disorderly"></div></div></div><div><div xs="4"><div><select required="" label="[object Object]" name="category_id" placeholder="Select a category" dataoptions="[object Object],[object Object]"></select></div></div></div><div><div xs="6"><textarea required="" label="[object Object]" name="description" style="width: 100%; height: 200px;">Initial description</textarea></div></div></div></div><div><button><span>cancel-button</span></button><button buttonstyle="primary"><span>save-and-close-button</span></button></div></div>"`;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`renders without crashing (snapshot) 1`] = `"<div><div panetitle="[object Object]" defaultwidth="fill"><div><div xs="10"><button buttonstyle="primary"><span>settings.incident-types-new-button</span></button></div></div><ul><li><li>Type 1 - Disorderly</li><li>Type 2 - Theft</li><li>Type 2.1 - Minor</li><li>Type 10 - Assault</li></li></ul></div><div>Mock GetIncidentTypesDetails</div><div></div></div>"`;