@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,538 @@
1
+ import React, { useEffect, useState, useRef } from 'react';
2
+ import DOMPurify from 'dompurify';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { useParams, useHistory } from 'react-router-dom';
5
+ import {
6
+ AccordionSet,
7
+ Accordion,
8
+ Button,
9
+ Checkbox,
10
+ Col,
11
+ Dropdown,
12
+ DropdownMenu,
13
+ Editor,
14
+ Headline,
15
+ Icon,
16
+ Label,
17
+ List,
18
+ MessageBanner,
19
+ Pane,
20
+ PaneHeader,
21
+ PaneMenu,
22
+ Row,
23
+ TextField
24
+ } from '@folio/stripes/components';
25
+ import GetTrespassTemplates from './GetTrespassTemplates';
26
+ import PutTrespassTemplate from './PutTrespassTemplate';
27
+ import ModalTrespassDocTokens from './ModalTrespassDocTokens';
28
+ import ModalPreviewTrespassDoc from './ModalPreviewTrespassDoc';
29
+ import stripHTML from '../components/incidents/helpers/stripHTML';
30
+ import DOMPurify from 'dompurify';
31
+ import { tokensArray, tokenValues } from './data/templateTokens';
32
+ import { useIncidents } from '../contexts/IncidentContext';
33
+
34
+ const TrespassDocEditPane = ({handleCancelEdit, handleCloseEdit, ...props}) => {
35
+ const { id } = useParams();
36
+ const history = useHistory();
37
+ const {
38
+ trespassTemplates
39
+ } = useIncidents();
40
+
41
+ const [showTokensModal, setShowTokensModal] = useState(false);
42
+ const [selectedTokens, setSelectedTokens] = useState([]);
43
+ const [showPreviewModal, setShowPreviewModal] = useState(false);
44
+ const [previewContent, setPreviewContent] = useState('');
45
+ const [lastRange, setLastRange] = useState(null);
46
+ const [formattedConfigTemplates, setFormattedConfigTemplates] = useState(null);
47
+ const [showErrorBanner, setShowErrorBanner] = useState(false);
48
+ const [isButtonDisabled, setIsButtonDisabled] = useState(false);
49
+ const [originalName, setOriginalName] = useState('');
50
+ const [formData, setFormData] = useState({
51
+ id: '',
52
+ name: '',
53
+ active: false,
54
+ isDefault: false,
55
+ description: '',
56
+ templateValue: ''
57
+ });
58
+
59
+ useEffect(() => {
60
+ if (trespassTemplates && trespassTemplates.length > 0) {
61
+ const foundTemplate = trespassTemplates.find((template) => template.id === id);
62
+ if (foundTemplate) {
63
+ setFormData({
64
+ id: foundTemplate.id,
65
+ name: foundTemplate.name || '',
66
+ active: foundTemplate.active || false,
67
+ isDefault: foundTemplate.isDefault || false,
68
+ description: foundTemplate.description || '', // not required
69
+ templateValue: foundTemplate.templateValue || ''
70
+ });
71
+ setOriginalName(foundTemplate.name || '');
72
+ setPreviewContent(foundTemplate.templateValue);
73
+ } else {
74
+ console.log(`template with id ${id} not found`);
75
+ setFormData({
76
+ name: '',
77
+ active: '',
78
+ description: '',
79
+ templateValue: ''
80
+ });
81
+ setPreviewContent('');
82
+ };
83
+ };
84
+ }, [id, trespassTemplates]);
85
+
86
+ const editorRef = useRef(null);
87
+
88
+ // save current quill current selection
89
+ const saveCurrentSelection = () => {
90
+ const quill = editorRef.current?.getEditor?.();
91
+ if (quill) {
92
+ const range = quill.getSelection();
93
+ if (range) {
94
+ setLastRange(range)
95
+ }
96
+ }
97
+ };
98
+
99
+ const handleInsertClick = () => {
100
+ saveCurrentSelection();
101
+ setShowTokensModal(true)
102
+ };
103
+
104
+ const handleInsertTokens = () => {
105
+ // handleTokenSelect(selectedTokens);
106
+ setShowTokensModal(false);
107
+ setTimeout(() => {
108
+ const quill = editorRef.current?.getEditor?.();
109
+ if (quill) {
110
+ quill.focus();
111
+ if (lastRange) {
112
+ quill.setSelection(lastRange);
113
+ const tokensText = selectedTokens.join(' ');
114
+ quill.insertText(lastRange.index, tokensText);
115
+ quill.setSelection(lastRange.index + tokensText.length)
116
+ } else {
117
+ const tokensText = selectedTokens.join(' ');
118
+ quill.insertText(quill.getLength(), tokensText);
119
+ quill.setSelection(quill.getLength());
120
+ }
121
+ }
122
+ }, 0)
123
+ // clear selected tokens for next use
124
+ setSelectedTokens([])
125
+ };
126
+
127
+ const getContentForSaveUpdate = () => {
128
+ const quill = editorRef.current?.getEditor?.();
129
+ if (quill) {
130
+ const contentHtml = quill.root.innerHTML;
131
+ // console.log("@getContentForSaveUpdate - contentHtml: ", contentHtml)
132
+ return contentHtml;
133
+ } else {
134
+ console.error('quill instance not available')
135
+ return null;
136
+ }
137
+ };
138
+
139
+ const getProcessedContent = () => {
140
+ const quill = editorRef.current?.getEditor?.();
141
+ if (quill) {
142
+ const contentHtml = quill.root.innerHTML;
143
+ let processedContent = contentHtml;
144
+
145
+ Object.keys(tokenValues).forEach((token) => {
146
+ const value = tokenValues[token];
147
+ // replace special chars as escaped
148
+ const regex = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
149
+ // replace regex with matched value
150
+ processedContent = processedContent.replace(regex, value);
151
+ })
152
+ return processedContent;
153
+ } else {
154
+ console.error('quill instance not available')
155
+ return null;
156
+ }
157
+ };
158
+
159
+ const handlePreviewClick = () => {
160
+ setShowPreviewModal(true);
161
+ saveCurrentSelection();
162
+ setPreviewContent(getProcessedContent());
163
+ };
164
+
165
+ const closePreviewModal = () => {
166
+ setShowPreviewModal(false);
167
+ setTimeout(() => {
168
+ const quill = editorRef.current?.getEditor?.();
169
+ if (quill) {
170
+ quill.focus();
171
+ if (lastRange) {
172
+ // set to last range
173
+ quill.setSelection(lastRange);
174
+ } else {
175
+ // else the length of Editor
176
+ quill.setSelection(quill.getLength());
177
+ }
178
+ }
179
+ }, 0)
180
+ };
181
+
182
+ useEffect(() => {
183
+ const quill = editorRef.current?.getEditor?.();
184
+ if (quill) {
185
+ const handler = (range, oldRange, source) => {
186
+ // console.log('selection-change:', { range, oldRange, source });
187
+ if (range) {
188
+ setLastRange(range)
189
+ };
190
+ // no update if lastRange is null
191
+ };
192
+
193
+ quill.on('selection-change', handler);
194
+
195
+ // clean up on unmount or when quill changes
196
+ return () => {
197
+ quill.off('selection-change', handler)
198
+ };
199
+ }
200
+ }, [editorRef.current]);
201
+
202
+ const handleChange = (event) => {
203
+ const { name, value } = event.target
204
+ if (name === 'active') {
205
+ setFormData((prev) => ({
206
+ ...prev,
207
+ [name]: event.target.checked
208
+ }));
209
+ } else if (name === 'isDefault') {
210
+ setFormData((prev) => ({
211
+ ...prev,
212
+ [name]: event.target.checked
213
+ }));
214
+ } else {
215
+ setFormData((prev) => ({
216
+ ...prev,
217
+ [name]: value
218
+ }));
219
+ };
220
+ };
221
+
222
+ const readyEditedTrespassTemplateObj = (processedTemplate) => {
223
+ const sanitizedContent = DOMPurify.sanitize(processedTemplate);
224
+ return {
225
+ id: formData.id,
226
+ name: formData.name.trim(),
227
+ active: formData.isDefault ? true : formData.active,
228
+ isDefault: formData.isDefault,
229
+ description: formData.description.trim(),
230
+ templateValue: sanitizedContent
231
+ }
232
+ };
233
+
234
+ const resetForFreshTemplateInput = () => {
235
+ setPreviewContent('');
236
+ setFormData({
237
+ id: '',
238
+ name: '',
239
+ active: false,
240
+ isDefault: false,
241
+ description: '',
242
+ templateValue: ''
243
+ });
244
+ const quill = editorRef.current?.getEditor?.();
245
+ if (quill) {
246
+ quill.setText('');
247
+ quill.setContents([]);
248
+ quill.history.clear(); // clear undo stack
249
+ quill.setSelection(0); // cursor to first index
250
+ } else {
251
+ console.error('Quill instance not available');
252
+ };
253
+ setLastRange(null);
254
+ handleCloseEdit(id)
255
+ };
256
+
257
+ const handleSaveTemplate = (e) => {
258
+ if (e) e.preventDefault();
259
+ // obtain ready template
260
+ const readyTemplate = getContentForSaveUpdate();
261
+ // console.log("readyTemplate: ", readyTemplate)
262
+ // make ready template object
263
+ const editedTemplate = readyEditedTrespassTemplateObj(readyTemplate);
264
+
265
+
266
+ // init array
267
+ let updatedTrespassTemplates = [];
268
+ // if edited template is the default template
269
+ if (editedTemplate.isDefault) {
270
+ // convert previous template of default, if any, to 'false' (not default)
271
+ const removedPrevDefaultList = trespassTemplates.map((template) => {
272
+ // ensure if editedTemplate.isDefault === true that it is the only default template
273
+ return { ...template, isDefault: false }
274
+ });
275
+ // merge edited template with updated list
276
+ updatedTrespassTemplates = [...removedPrevDefaultList, editedTemplate];
277
+ } else {
278
+ updatedTrespassTemplates = [...trespassTemplates, editedTemplate]
279
+ };
280
+
281
+ // remove prev version of newly edited template (it's not an update, its a replace)
282
+ const filteredList = updatedTrespassTemplates.filter((template) => template.id !== editedTemplate.id );
283
+
284
+ const readyFormattedData = {
285
+ value: {
286
+ templates: [...filteredList, editedTemplate]
287
+ }
288
+ };
289
+
290
+ console.log("readyFormattedData -->", JSON.stringify(readyFormattedData, null, 2));
291
+
292
+ setFormattedConfigTemplates({ data: readyFormattedData });
293
+ setTimeout(() => {
294
+ resetForFreshTemplateInput();
295
+ }, 800);
296
+ };
297
+
298
+ const handleMessageBannerEntered = () => {
299
+ setTimeout(() => {
300
+ setShowErrorBanner(false)
301
+ }, 2800)
302
+ };
303
+
304
+ // helper for useEffect to check state not empty
305
+ // (at readyEditedTrespassTemplateObj for PUT the value of getProcessedContent()
306
+ // is used for templateValue not formData.templateValue)
307
+ const handleEditorChange = (content) => {
308
+ const sanitizedContent = DOMPurify.sanitize(content);
309
+ setFormData(prev => ({
310
+ ...prev,
311
+ templateValue: sanitizedContent
312
+ }))
313
+ };
314
+
315
+ const normalizeTemplateName = (name) => {
316
+ return name.toLowerCase().replace(/[\s\-_.,'"]/g, '');
317
+ };
318
+
319
+ useEffect(() => {
320
+ const isNameNotEmpty = formData.name && formData.name.trim() !== '';
321
+ const normalizedCurrentName = normalizeTemplateName(formData.name);
322
+ const normalizedOriginalName = normalizeTemplateName(originalName);
323
+ const isNameChanged = normalizedCurrentName !== normalizedOriginalName;
324
+ const isNameUnique = !trespassTemplates.some((template) =>
325
+ template.id !== formData.id &&
326
+ normalizeTemplateName(template.name) === normalizedCurrentName
327
+ );
328
+ const isEditorValid = stripHTML(formData.templateValue).trim() !== '';
329
+ setShowErrorBanner(isNameNotEmpty && !isNameUnique && isNameChanged);
330
+ const isValid = isNameNotEmpty && isEditorValid && (isNameUnique || !isNameChanged);
331
+
332
+ setIsButtonDisabled(!isValid);
333
+ }, [formData, trespassTemplates, originalName]);
334
+
335
+ const modules = {
336
+ toolbar: {
337
+ container: '#toolbar-edit'
338
+ },
339
+ clipboard: {
340
+ matchVisual: false
341
+ }
342
+ };
343
+
344
+ const formats = [
345
+ 'bold', 'italic', 'underline', 'strike',
346
+ 'header', 'script', 'list', 'bullet',
347
+ 'indent', 'align',
348
+ ];
349
+
350
+ const templateName = formData.name;
351
+
352
+ return (
353
+ <Pane
354
+ dismissible
355
+ onClose={() => handleCancelEdit(id)}
356
+ defaultWidth="100%"
357
+ paneTitle={<FormattedMessage
358
+ id="settings.trespass-document-edit-paneTitle"
359
+ values={{ templateName }}
360
+ />}
361
+ {...props}>
362
+
363
+ <GetTrespassTemplates />
364
+
365
+ {formattedConfigTemplates &&
366
+ <PutTrespassTemplate
367
+ data={formattedConfigTemplates}
368
+ />}
369
+
370
+ {showTokensModal &&
371
+ <ModalTrespassDocTokens
372
+ setShowTokensModal={setShowTokensModal}
373
+ selectedTokens={selectedTokens}
374
+ setSelectedTokens={setSelectedTokens}
375
+ handleInsertTokens={handleInsertTokens}
376
+ tokensArray={tokensArray}
377
+ />}
378
+
379
+ {showPreviewModal &&
380
+ <ModalPreviewTrespassDoc
381
+ previewContent={previewContent}
382
+ closePreviewModal={closePreviewModal}
383
+ />}
384
+
385
+ <AccordionSet>
386
+ <Accordion label={<FormattedMessage
387
+ id="details-pane.accordion-label.details"/>}
388
+ >
389
+ <Row>
390
+ <Col xs={6}>
391
+ <TextField
392
+ required
393
+ label={<FormattedMessage id="settings.trespass-document-edit-textfield-name-label"/>}
394
+ name='name'
395
+ value={formData.name}
396
+ onChange={handleChange}
397
+ />
398
+ </Col>
399
+ </Row>
400
+ <Row>
401
+ <Col xs={6}>
402
+ <TextField
403
+ label={<FormattedMessage id="settings.trespass-document-edit-textfield-description-label"/>}
404
+ name='description'
405
+ value={formData.description}
406
+ onChange={handleChange}
407
+ />
408
+ </Col>
409
+ </Row>
410
+ <Row>
411
+ <Col xs={6}>
412
+ <Checkbox
413
+ label={<FormattedMessage id="settings.trespass-document-edit-checkbox-active-label"/>}
414
+ name='active'
415
+ checked={formData.active}
416
+ onChange={handleChange}
417
+ />
418
+ </Col>
419
+ </Row>
420
+ <Row>
421
+ <Col xs={6}>
422
+ <Checkbox
423
+ label={<FormattedMessage id="settings.trespass-document-edit-checkbox-default-label"/>}
424
+ name='isDefault'
425
+ checked={formData.isDefault}
426
+ onChange={handleChange}
427
+ />
428
+ </Col>
429
+ </Row>
430
+ <Row style={{ marginTop: '25px', marginBottom: '25px' }}>
431
+ <Col xs={8}>
432
+ <div style={{ minHeight: '50px' }}>
433
+ <MessageBanner
434
+ onEntered={() => handleMessageBannerEntered()}
435
+ type="error"
436
+ show={showErrorBanner}>
437
+ <FormattedMessage id="settings.trespass-document-template.message-banner.name-must-be-unique"/>
438
+ </MessageBanner>
439
+ </div>
440
+ </Col>
441
+ </Row>
442
+ </Accordion>
443
+
444
+ <Accordion label='Document'>
445
+ <Row style={{ marginTop: '20px'}}>
446
+ <Col xs={12}>
447
+ <div id='toolbar-edit'>
448
+ {/* text formatting */}
449
+ <span className='ql-formats'>
450
+ <button className='ql-bold'></button>
451
+ <button className='ql-italic'></button>
452
+ <button className='ql-underline'></button>
453
+ <button className='ql-strike'></button>
454
+ </span>
455
+
456
+ {/* headers */}
457
+ <span className='ql-formats'>
458
+ <select className='ql-header'>
459
+ <option value='1'></option>
460
+ <option value='2'></option>
461
+ <option selected></option>
462
+ </select>
463
+ </span>
464
+
465
+ {/* subscript/superscript */}
466
+ <span className='ql-formats'>
467
+ <button className='ql-script' value='sub'></button>
468
+ <button className='ql-script' value='super'></button>
469
+ </span>
470
+
471
+ {/* lists */}
472
+ <span className='ql-formats'>
473
+ <button className='ql-list' value='ordered'></button>
474
+ <button className='ql-list' value='bullet'></button>
475
+ </span>
476
+
477
+ {/* indentation */}
478
+ <span className='ql-formats'>
479
+ <button className='ql-indent' value='-1'></button>
480
+ <button className='ql-indent' value='+1'></button>
481
+ </span>
482
+
483
+ {/* text alignment */}
484
+ <span className='ql-formats'>
485
+ <select className='ql-align'>
486
+ <option selected></option>
487
+ <option value='center'></option>
488
+ <option value='right'></option>
489
+ <option value='justify'></option>
490
+ </select>
491
+ </span>
492
+
493
+ {/* custom button */}
494
+ <span className='ql-formats'>
495
+ <button
496
+ id='custom-button'
497
+ onClick={handleInsertClick}
498
+ >
499
+ <FormattedMessage id="settings.trespass-document-edit-insert-button"/>
500
+ </button>
501
+ </span>
502
+ </div>
503
+ <Editor
504
+ required
505
+ value={formData.templateValue}
506
+ formats={formats}
507
+ modules={modules}
508
+ editorRef={editorRef}
509
+ onChange={handleEditorChange}
510
+ style={{ whiteSpace: 'pre-wrap' }}
511
+ />
512
+ </Col>
513
+ </Row>
514
+
515
+ <Row style={{ marginTop: '20px'}}>
516
+ <Col xs={4}>
517
+ <Button
518
+ onClick={handlePreviewClick}
519
+ >
520
+ <FormattedMessage id="settings.trespass-document-edit-preview-button"/>
521
+ </Button>
522
+ </Col>
523
+ <Col xs={4}>
524
+ <Button
525
+ buttonStyle='primary'
526
+ disabled={isButtonDisabled}
527
+ onClick={handleSaveTemplate}>
528
+ <FormattedMessage id="save-and-close-button"/>
529
+ </Button>
530
+ </Col>
531
+ </Row>
532
+ </Accordion>
533
+ </AccordionSet>
534
+ </Pane>
535
+ );
536
+ };
537
+
538
+ export default TrespassDocEditPane;