@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,187 @@
1
+ /**
2
+ * EditPane.test.js
3
+ * Snapshot + smoke tests for the EditPane container.
4
+ */
5
+ import React from 'react';
6
+ import { act } from 'react-dom/test-utils';
7
+ import { createRoot } from 'react-dom/client';
8
+ import EditPane from './EditPane';
9
+
10
+ /* ------------------------------------------------------------------ *
11
+ * 1. react-intl / router / stripes mocks *
12
+ * ------------------------------------------------------------------ */
13
+ jest.mock('react-intl', () => ({
14
+ useIntl : () => ({ formatMessage: ({ id }) => id }),
15
+ FormattedMessage : (p) => <span>{p.id}</span>,
16
+ }));
17
+ jest.mock('react-router-dom', () => ({
18
+ useParams : () => ({ id: 'edit-id' }),
19
+ useHistory : () => ({ push: jest.fn(), replace: jest.fn() }),
20
+ useLocation : () => ({ pathname: '/incidents/edit-id', search: '' }),
21
+ }));
22
+ jest.mock('@folio/stripes/core', () => ({
23
+ useStripes: () => ({ hasPerm: jest.fn(() => true) }),
24
+ }));
25
+
26
+ /* ------------------------------------------------------------------ *
27
+ * 2. stripes/components mock (minimal DOM) *
28
+ * ------------------------------------------------------------------ */
29
+ jest.mock('@folio/stripes/components', () => {
30
+ const React = require('react');
31
+ const mk = (tag) => (p) => React.createElement(tag, p, p.children);
32
+
33
+ /* need PaneHeader/PaneFooter to render footer buttons */
34
+ return {
35
+ Accordion : mk('div'),
36
+ AccordionSet : mk('div'),
37
+ Button : mk('button'),
38
+ Checkbox : mk('div'),
39
+ Col : mk('div'),
40
+ Datepicker : mk('input'),
41
+ Editor : mk('textarea'),
42
+ ExpandAllButton : mk('button'),
43
+ Icon : (p) => <span {...p}>{p.icon}</span>,
44
+ Label : mk('label'),
45
+ List : (p) => <ul>{(p.items||[]).map((x,i)=><li key={i}>{x}</li>)}</ul>,
46
+ LoadingPane : mk('div'),
47
+ MetaSection : mk('div'),
48
+ MessageBanner : mk('div'),
49
+ Pane : mk('div'),
50
+ PaneHeader : mk('div'),
51
+ PaneFooter : mk('div'),
52
+ Row : mk('div'),
53
+ Select : mk('select'),
54
+ TextArea : mk('textarea'),
55
+ Timepicker : mk('input'),
56
+ };
57
+ });
58
+
59
+ /* ------------------------------------------------------------------ *
60
+ * 3. Child-component stubs *
61
+ * ------------------------------------------------------------------ */
62
+ jest
63
+ .mock('../../settings/GetLocationsInService', () => () => <div>Mock GetLocationsInService</div>)
64
+ .mock('../../settings/GetIncidentTypesDetails', () => () => <div>Mock GetIncidentTypesDetails</div>)
65
+ .mock('./ModalSelectKnownCustomer', () => () => <div>Mock ModalSelectKnownCustomer</div>)
66
+ .mock('./ModalDescribeCustomer', () => () => <div>Mock ModalDescribeCustomer</div>)
67
+ .mock('./ModalSelectIncidentTypes', () => () => <div>Mock ModalSelectIncidentTypes</div>)
68
+ .mock('./ModalSelectWitness', () => () => <div>Mock ModalSelectWitness</div>)
69
+ .mock('./ModalTrespass', () => () => <div>Mock ModalTrespass</div>)
70
+ .mock('./ModalCustomerDetails', () => () => <div>Mock ModalCustomerDetails</div>)
71
+ .mock('./ModalAddMedia', () => () => <div>Mock ModalAddMedia</div>)
72
+ .mock('./CreateMedia', () => () => <div>Mock CreateMedia</div>)
73
+ .mock('./GetDetails', () => () => <div>Mock GetDetails</div>)
74
+ .mock('./GetListDQLinkIncident', () => () => <div>Mock GetListDQLinkIncident</div>)
75
+ .mock('./GEtOrgLocaleSettings', () => () => <div>Mock GEtOrgLocaleSettings</div>)
76
+ .mock('./GetSelf', () => () => <div>Mock GetSelf</div>)
77
+ .mock('./GetMedia', () => () => <div>Mock GetMedia</div>)
78
+ .mock('./GetName', () => () => <div>Mock GetName</div>)
79
+ .mock('./GetLocations', () => () => <div>Mock GetLocations</div>)
80
+ .mock('./GetSummary', () => () => <div>Mock GetSummary</div>)
81
+ .mock('./GetNameCreatedBy', () => () => <div>Mock GetNameCreatedBy</div>)
82
+ .mock('./Thumbnail', () => () => <div>Mock Thumbnail</div>)
83
+ .mock('./ThumbnailSkeleton', () => () => <div>Mock ThumbnailSkeleton</div>)
84
+ .mock('./ThumbnailMarkRemoval', () => () => <div>Mock ThumbnailMarkRemoval</div>)
85
+ .mock('./ThumbnailTempPreSave', () => () => <div>Mock ThumbnailTempPreSave</div>)
86
+ .mock('./UpdateReport', () => () => <div>Mock UpdateReport</div>)
87
+ .mock('./ModalCustomWitness', () => () => <div>Mock ModalCustomWitness</div>)
88
+ .mock('../../settings/GetTrespassTemplates', () => () => <div>Mock GetTrespassTemplates</div>)
89
+ .mock('../../settings/GetTrespassReasons', () => () => <div>Mock GetTrespassReasons</div>);
90
+
91
+ /* ------------------------------------------------------------------ *
92
+ * 4. Simple helper mocks *
93
+ * ------------------------------------------------------------------ */
94
+ jest.mock('./helpers/convertUTCISOToPrettyDate', () => jest.fn((d)=>d));
95
+ jest.mock('./helpers/convertUTCISOToLocalePrettyTime', () => jest.fn((d)=>d));
96
+ jest.mock('./helpers/parseMMDDYYYY', () => jest.fn((d)=>new Date(d)));
97
+ jest.mock('./helpers/isValidDateFormat',() => jest.fn(()=>true));
98
+ jest.mock('./helpers/isValidTimeInput', () => jest.fn(()=>true));
99
+ jest.mock('./helpers/formatDateToUTCISO', () => jest.fn((d)=>d));
100
+ jest.mock('./helpers/formatDateAndTimeToUTCISO', () => jest.fn((d,t)=>`${d}T${t}`));
101
+ jest.mock('./helpers/stripHTML', () => jest.fn((s)=>s));
102
+ jest.mock('./helpers/getTodayDate', () => jest.fn(()=> '01/01/2020'));
103
+ jest.mock('../../settings/helpers/makeId', () => () => 'mockedId');
104
+
105
+ /* ------------------------------------------------------------------ *
106
+ * 5. IncidentContext mock (defined inside the factory) *
107
+ * ------------------------------------------------------------------ */
108
+ jest.mock('../../contexts/IncidentContext', () => {
109
+ const context = {
110
+ singleIncident : {
111
+ id: 'edit-id',
112
+ customerNa : false,
113
+ customers : [],
114
+ incidentLocation : 'loc-1',
115
+ subLocation : '',
116
+ dateTimeOfIncident : '2020-01-01T12:00:00Z',
117
+ isApproximateTime : false,
118
+ detailedDescriptionOfIncident: 'desc',
119
+ incidentWitnesses : [],
120
+ incidentTypes : [],
121
+ attachments : [],
122
+ metadata : {},
123
+ staffSuppressed : false,
124
+ },
125
+ closeEditPane : jest.fn(),
126
+ openModalSelectTypes : jest.fn(),
127
+ openModalUnknownCust : jest.fn(),
128
+ openModalSelectKnownCust: jest.fn(),
129
+ selectedCustomers : [],
130
+ setSelectedCustomers : jest.fn(),
131
+ selectedWitnesses : [],
132
+ setSelectedWitnesses : jest.fn(),
133
+ openModalSelectWitness : jest.fn(),
134
+ self : { id:'self', firstName:'Self', lastName:'User' },
135
+ openModalTrespass : jest.fn(),
136
+ isLoadingDetails : false,
137
+ openLoadingDetails : jest.fn(),
138
+ isUpdatingReport : false,
139
+ setIsUpdatingReport : jest.fn(),
140
+ openModalMedia : jest.fn(),
141
+ setAttachmentsData : jest.fn(),
142
+ idForMediaCreate : null,
143
+ setIdForMediaCreate : jest.fn(),
144
+ formDataArrayForMediaCreate : null,
145
+ setFormDataArrayForMediaCreate: jest.fn(),
146
+ openModalCustomerDetails: jest.fn(),
147
+ locationsInService : [],
148
+ incidentTypesList : [],
149
+ isImageArrayLoading : false,
150
+ openImageSkeleton : jest.fn(),
151
+ closeImageSkeleton : jest.fn(),
152
+ openModalCustomWitness : jest.fn(),
153
+ trespassTemplates : [],
154
+ triggerDocumentError : jest.fn(),
155
+ };
156
+ return { useIncidents: () => context };
157
+ });
158
+
159
+ /* ------------------------------------------------------------------ *
160
+ * 6. DOM setup / teardown *
161
+ * ------------------------------------------------------------------ */
162
+ let container, root;
163
+ beforeEach(() => {
164
+ container = document.createElement('div');
165
+ document.body.appendChild(container);
166
+ root = createRoot(container);
167
+ });
168
+ afterEach(() => {
169
+ root.unmount();
170
+ document.body.removeChild(container);
171
+ container = null;
172
+ });
173
+
174
+ /* ------------------------------------------------------------------ *
175
+ * 7. Tests *
176
+ * ------------------------------------------------------------------ */
177
+ it('renders without crashing (snapshot)', async () => {
178
+ await act(async () => { root.render(<EditPane />); });
179
+ await act(async () => {});
180
+ expect(container.innerHTML).toMatchSnapshot();
181
+ });
182
+
183
+ it('mounts key child components', () => {
184
+ act(() => { root.render(<EditPane />); });
185
+ expect(container.textContent).toContain('Mock GetDetails');
186
+ expect(container.textContent).toContain('Mock GetLocationsInService');
187
+ });
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { stripesConnect } from '@folio/stripes/core';
4
+ import { IncidentContext } from '../../contexts/IncidentContext';
5
+
6
+ class GetDetails extends React.Component {
7
+ static contextType = IncidentContext;
8
+
9
+ static manifest = Object.freeze({
10
+ incident: {
11
+ type: 'okapi',
12
+ path: (_q, _p, _r, _logger, props) => `incidents/${props.id}`,
13
+ records: '',
14
+ throwErrors: false,
15
+ accumulate: false
16
+ },
17
+ });
18
+
19
+ static propTypes = {
20
+ id: PropTypes.string,
21
+ resources: PropTypes.object.isRequired,
22
+ };
23
+
24
+ componentDidMount () {
25
+ this.trySetRecord(this.props);
26
+ };
27
+
28
+ componentDidUpdate(prevProps) {
29
+ if (
30
+ this.props.id !== prevProps.id || // navigation
31
+ this.props.resources.incident !== prevProps.resources.incident // load complete
32
+ ) {
33
+ this.trySetRecord(this.props);
34
+ }
35
+ };
36
+
37
+ trySetRecord({ resources }) {
38
+ this.context.openLoadingDetails();
39
+ const { incident } = resources;
40
+ if (!incident?.hasLoaded || !incident.records?.length) return;
41
+ // console.log("trySetRecord - incident --> ", JSON.stringify(incident, null, 2));
42
+
43
+ // console.log("trySetRecord - incident.records[0] --> ", JSON.stringify(incident.records[0], null, 2));
44
+
45
+ this.context.setSingleIncident(incident.records[0]);
46
+ this.context.closeLoadingDetails();
47
+ };
48
+
49
+ render() {
50
+ return null;
51
+ };
52
+ };
53
+
54
+ GetDetails.contextType = IncidentContext;
55
+ export default stripesConnect(GetDetails, '@spokane-folio/security-incident');
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { stripesConnect } from '@folio/stripes/core';
4
+ import { getHeaderWithCredentials } from '@folio/stripes/util';
5
+ import { IncidentContext } from '../../contexts/IncidentContext';
6
+
7
+ class GetListDQLinkIncident extends React.Component {
8
+ static contextType = IncidentContext;
9
+
10
+ constructor(props) {
11
+ super(props);
12
+ const { stripes } = this.props;
13
+ const { okapi } = stripes;
14
+ this.okapiURL = okapi.url;
15
+ };
16
+
17
+ static propTypes = {
18
+ stripes: PropTypes.object.isRequired,
19
+ history : PropTypes.object,
20
+ };
21
+
22
+ componentDidMount() { this.fetchListDQLinkIncident(); }
23
+ componentDidUpdate() {
24
+ if (this.props.queryStringLinkIncident !== this.lastQuery) this.fetchListDQLinkIncident();
25
+ }
26
+
27
+ fetchListDQLinkIncident = async () => {
28
+ const { stripes } = this.props;
29
+ const credsObj = getHeaderWithCredentials(stripes.okapi);
30
+ const qs = this.props.queryStringLinkIncident;
31
+ this.lastQuery = qs;
32
+
33
+ this.context.openLoadingSearch();
34
+
35
+ // console.log("@fetchListDQLinkIncident - qs --> ", qs)
36
+
37
+ const mergedHeaders = {
38
+ ...credsObj.headers,
39
+ }
40
+
41
+ const options = {
42
+ ...credsObj,
43
+ headers: mergedHeaders
44
+ }
45
+
46
+ try {
47
+ const resp = await fetch(
48
+ `${this.okapiURL}/incidents?${qs}`,
49
+ options
50
+ );
51
+
52
+ if (!resp.ok) throw new Error(`server ${resp.status}`);
53
+
54
+ const json = await resp.json();
55
+
56
+ this.props.setIncidentsListForLink(prev => {
57
+ if (this.props.offset === 0) return json.incidents; // first page replaces
58
+ const next = [...prev, ...json.incidents];
59
+ // dedupe by id
60
+ const seen = new Set();
61
+ return next.filter(r => {
62
+ const id = r.id || r._id;
63
+ if (!id) return true;
64
+ if (seen.has(id)) return false;
65
+ seen.add(id);
66
+ return true;
67
+ });
68
+ });
69
+ this.props.setTotalResultsForLink(json.totalRecords);
70
+
71
+ } catch (err) {
72
+ console.error('@fetchListDQLinkIncident - incident fetch failed', err);
73
+ } finally {
74
+ this.context.closeLoadingSearch();
75
+ }
76
+ };
77
+
78
+ render() { return null; }
79
+ };
80
+
81
+ export default stripesConnect(GetListDQLinkIncident,'@spokane-folio/security-incident');
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { stripesConnect } from '@folio/stripes/core';
4
+ import { getHeaderWithCredentials } from '@folio/stripes/util';
5
+ import { IncidentContext } from '../../contexts/IncidentContext';
6
+
7
+ class GetListDynamicQuery extends React.Component {
8
+ static contextType = IncidentContext;
9
+
10
+ constructor(props) {
11
+ super(props);
12
+ const { stripes } = this.props;
13
+ const { okapi } = stripes;
14
+ this.okapiURL = okapi.url;
15
+ };
16
+
17
+ static propTypes = {
18
+ stripes: PropTypes.object.isRequired,
19
+ history : PropTypes.object,
20
+ };
21
+
22
+ componentDidMount() { this.fetchListDynamicQuery(); }
23
+ componentDidUpdate() {
24
+ if (this.context.queryString !== this.lastQuery) this.fetchListDynamicQuery();
25
+ }
26
+
27
+ fetchListDynamicQuery = async () => {
28
+ const { stripes } = this.props;
29
+ const credsObj = getHeaderWithCredentials(stripes.okapi);
30
+ const qs = this.context.queryString;
31
+ this.lastQuery = qs;
32
+
33
+ this.context.openLoadingSearch();
34
+
35
+ const mergedHeaders = {
36
+ ...credsObj.headers, // tenant / token / content-type
37
+ }
38
+
39
+ const options = {
40
+ ...credsObj,
41
+ headers: mergedHeaders
42
+ }
43
+
44
+ try {
45
+ const resp = await fetch(
46
+ `${this.okapiURL}/incidents?${qs}`,
47
+ options
48
+ );
49
+
50
+ if (!resp.ok) throw new Error(`server ${resp.status}`);
51
+
52
+ const json = await resp.json();
53
+ this.context.setTotalResults(json.totalRecords);
54
+ this.context.setIncidentsList(json.incidents);
55
+ } catch (err) {
56
+ console.error('incident fetch failed', err);
57
+ } finally {
58
+ this.context.closeLoadingSearch();
59
+ }
60
+ };
61
+
62
+ render() { return null; }
63
+ };
64
+
65
+
66
+ export default stripesConnect(GetListDynamicQuery,'@spokane-folio/security-incident');
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { stripesConnect } from '@folio/stripes/core';
4
+ import { IncidentContext } from '../../contexts/IncidentContext';
5
+ class GetLocations extends React.Component {
6
+ static contextType = IncidentContext;
7
+
8
+ static manifest = Object.freeze({
9
+ locations: {
10
+ type: 'okapi',
11
+ path: 'locations?limit=100',
12
+ accumulate: true,
13
+ },
14
+ });
15
+
16
+ static propTypes = {
17
+ locations: PropTypes.shape({
18
+ records: PropTypes.object,
19
+ }),
20
+
21
+ context: PropTypes.object,
22
+ mutator: PropTypes.shape({
23
+ locations: PropTypes.shape({
24
+ GET: PropTypes.func.isRequired,
25
+ }).isRequired,
26
+ }).isRequired,
27
+ // not required, used in context of <NewLocationsZones />
28
+ handleFetchedLocations: PropTypes.func
29
+ };
30
+
31
+ componentDidMount() {
32
+ this.fetchLocations();
33
+ }
34
+
35
+ fetchLocations() {
36
+ this.props.mutator.locations
37
+ .GET({ path: `locations?limit=100` })
38
+ .then((records) => {
39
+ const data = records.locations;
40
+ const names = data.map((record) => {
41
+ return record.discoveryDisplayName;
42
+ });
43
+ if (this.props.context === 'new-location-zones' || this.props.context === 'opt-in-locations') {
44
+ this.props.handleFetchedLocations(names);
45
+ }
46
+ this.context.setLocations(names);
47
+ });
48
+ }
49
+
50
+ render() {
51
+ return <></>;
52
+ }
53
+ }
54
+
55
+ GetLocations.contextType = IncidentContext;
56
+
57
+ export default stripesConnect(GetLocations, '@spokane-folio/security-incident');
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import { stripesConnect } from '@folio/stripes/core';
3
+ import { getHeaderWithCredentials } from '@folio/stripes/util';
4
+
5
+ class GetMedia extends React.Component {
6
+ constructor(props) {
7
+ super(props);
8
+ const { stripes } = this.props;
9
+ const { okapi } = stripes;
10
+ this.okapiURL = okapi.url;
11
+ };
12
+
13
+ componentDidMount() {
14
+ this.GetMedia()
15
+ };
16
+
17
+ componentDidUpdate(prevProps) {
18
+ if(this.props.id !== prevProps.id || this.props.imageId !== prevProps.imageId) {
19
+ this.GetMedia()
20
+ }
21
+ };
22
+
23
+ GetMedia = async () => {
24
+ const { id, imageId, mediaHandler, context, contentType } = this.props;
25
+ const { stripes } = this.props;
26
+ // console.log("stripes: ", JSON.stringify(stripes, null, 2))
27
+ const headersWithCredentials = getHeaderWithCredentials(stripes.okapi);
28
+ const { headers } = headersWithCredentials
29
+ // console.log("headersWithCredentials: ", JSON.stringify(headersWithCredentials, null, 2))
30
+ // console.log("headers: ", JSON.stringify(headers, null, 2))
31
+
32
+ if(context === 'thumbnail') {
33
+ if(contentType.startsWith('video')) {
34
+ const placeholderUrl = 'isVideo'
35
+ mediaHandler(placeholderUrl, this.props.imageId)
36
+ return;
37
+ };
38
+
39
+ try {
40
+ const response = await fetch(`${this.okapiURL}/incidents/${id}/media/${imageId}?format=thumbnail`, {
41
+ method: 'GET',
42
+ headers: {
43
+ ...headers
44
+ }
45
+ });
46
+ if(response.status === 200) {
47
+ const blob = await response.blob();
48
+ const mediaUrl = URL.createObjectURL(blob)
49
+ this.props.mediaHandler(mediaUrl, this.props.imageId)
50
+ }
51
+ } catch (error) {
52
+ console.error('@GetMedia thumbnail context - error: ', error)
53
+ }
54
+ } else if(context === 'original-or-video') {
55
+ try {
56
+ const response = await fetch(`${this.okapiURL}/incidents/${id}/media/${imageId}`, {
57
+ method: 'GET',
58
+ headers: {
59
+ ...headers
60
+ }
61
+ });
62
+ if(response.status === 200) {
63
+ const blob = await response.blob();
64
+ const mediaUrl = URL.createObjectURL(blob)
65
+ this.props.handleDataResponse(mediaUrl)
66
+ // this.props.mediaHandler(mediaUrl, this.props.imageId)
67
+ }
68
+ } catch (error) {
69
+ console.error('@GetMedia - error: ', error)
70
+ }
71
+ } else if(context === 'document') {
72
+ try {
73
+ const response = await fetch(`${this.okapiURL}/incidents/${id}/media/${imageId}`, {
74
+ method: 'GET',
75
+ headers: {
76
+ ...headers
77
+ }
78
+ });
79
+ if(response.status === 200) {
80
+ // console.log("context === 'document' RAN, the response: ", response)
81
+ const blob = await response.blob();
82
+ const mediaUrl = URL.createObjectURL(blob)
83
+ this.props.mediaHandler(mediaUrl, this.props.imageId)
84
+ }
85
+ } catch (error) {
86
+ console.error('@GetMedia - error: ', error)
87
+ }
88
+ } else {
89
+ console.log("@GetMedia - no context provided")
90
+ }
91
+ };
92
+
93
+ render() {
94
+ return null;
95
+ };
96
+ };
97
+
98
+ export default stripesConnect(GetMedia, '@spokane-folio/security-incident')
@@ -0,0 +1,111 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { stripesConnect } from '@folio/stripes/core';
4
+ import { IncidentContext } from '../../contexts/IncidentContext';
5
+
6
+ class GetName extends React.Component {
7
+ static contextType = IncidentContext;
8
+ static manifest = Object.freeze({
9
+ customer: {
10
+ type: 'okapi',
11
+ accumulate: true,
12
+ throwErrors: false // handle 4xx/5xx errors on our own
13
+ },
14
+ });
15
+
16
+ static propTypes = {
17
+ context: PropTypes.string,
18
+ uuid: PropTypes.string,
19
+ isCustomWitness: PropTypes.object,
20
+ handleGetWitnessName: PropTypes.func,
21
+ handleGetCustName: PropTypes.func,
22
+ handleGetCreatedByName: PropTypes.func,
23
+ handleGetUpdatedByName: PropTypes.func,
24
+ mutator: PropTypes.shape({
25
+ customer: PropTypes.shape({
26
+ GET: PropTypes.func.isRequired,
27
+ }).isRequired,
28
+ }).isRequired,
29
+ };
30
+
31
+ componentDidMount() {
32
+ this.fetchName();
33
+ };
34
+
35
+ componentDidUpdate(prevProps) {
36
+ if (this.props.uuid !== prevProps.uuid) {
37
+ this.fetchName();
38
+ }
39
+ };
40
+
41
+ fetchName() {
42
+ // if we have a custom witness, skip the network call:
43
+ if (this.props.isCustomWitness) {
44
+ const customWitnessRecord = {
45
+ id: this.props.isCustomWitness.id,
46
+ barcode: this.props.isCustomWitness.barcode,
47
+ firstName: this.props.isCustomWitness.firstName,
48
+ lastName: this.props.isCustomWitness.lastName,
49
+ role: this.props.isCustomWitness.role,
50
+ phone: this.props.isCustomWitness.phone,
51
+ email: this.props.isCustomWitness.email,
52
+ isCustom: this.props.isCustomWitness.isCustom
53
+ };
54
+ this.props.handleGetWitnessName(customWitnessRecord);
55
+ return;
56
+ }
57
+
58
+ // fetch customer at /users via uuid
59
+ if (this.props.uuid) {
60
+ this.props.mutator.customer
61
+ .GET({
62
+ path: `users/${this.props.uuid}`,
63
+ throwErrors: false
64
+ })
65
+ .then((response) => {
66
+ if (response && response.httpStatus === 404) {
67
+ console.log(`@GetName: user for uuid="${this.props.uuid}" not found.`);
68
+ return;
69
+ };
70
+
71
+
72
+ if (response && response.id) {
73
+ const refinedRecord = {
74
+ id: response.id,
75
+ barcode: response.barcode,
76
+ firstName: response.personal.firstName,
77
+ lastName: response.personal.lastName,
78
+ profilePicLinkOrUUID: response.personal.profilePictureLink ? response.personal.profilePictureLink : ''
79
+ };
80
+
81
+ if (this.props.context === 'customer') {
82
+ this.props.handleGetCustName(refinedRecord);
83
+ } else if (this.props.context === 'witness') {
84
+ this.props.handleGetWitnessName(refinedRecord);
85
+ } else if (this.props.context === 'createdBy') {
86
+ this.props.handleGetCreatedByName(refinedRecord);
87
+ } else if (this.props.context === 'updatedBy') {
88
+ this.props.handleGetUpdatedByName(refinedRecord);
89
+ } else {
90
+ console.log('@fetchName - something went wrong - no data context recognized');
91
+ }
92
+ };
93
+ })
94
+ .catch((error) => {
95
+ // if stripes-connect rejects the promise
96
+ if (error?.httpStatus === 404) {
97
+ console.log(`@GetName: user for uuid="${this.props.uuid}" not found.`);
98
+ this.props.handleMissingUsers(this.props.uuid)
99
+ return;
100
+ }
101
+ console.error('@GetName: unhandled error fetching user:', error);
102
+ });
103
+ };
104
+ };
105
+
106
+ render() {
107
+ return null;
108
+ };
109
+ };
110
+
111
+ export default stripesConnect(GetName, '@spokane-folio/security-incident');