@pega/react-sdk-overrides 24.2.11 → 25.1.11

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 (173) hide show
  1. package/lib/designSystemExtension/AlertBanner/AlertBanner.css +46 -0
  2. package/lib/designSystemExtension/AlertBanner/AlertBanner.tsx +37 -20
  3. package/lib/designSystemExtension/Banner/Banner.css +1 -1
  4. package/lib/designSystemExtension/Banner/Banner.tsx +10 -7
  5. package/lib/designSystemExtension/CaseSummaryFields/CaseSummaryFields.css +0 -1
  6. package/lib/designSystemExtension/CaseSummaryFields/CaseSummaryFields.tsx +53 -37
  7. package/lib/designSystemExtension/DetailsFields/DetailsFields.tsx +11 -13
  8. package/lib/designSystemExtension/FieldGroup/FieldGroup.tsx +8 -9
  9. package/lib/designSystemExtension/FieldGroupList/FieldGroupList.tsx +9 -9
  10. package/lib/designSystemExtension/FieldValueList/FieldValueList.tsx +7 -8
  11. package/lib/designSystemExtension/Operator/Operator.tsx +21 -19
  12. package/lib/designSystemExtension/Pulse/Pulse.tsx +1 -1
  13. package/lib/designSystemExtension/RichTextEditor/RichTextEditor.tsx +32 -4
  14. package/lib/designSystemExtension/WssQuickCreate/WssQuickCreate.css +7 -14
  15. package/lib/designSystemExtension/WssQuickCreate/WssQuickCreate.tsx +13 -2
  16. package/lib/field/AutoComplete/AutoComplete.tsx +1 -1
  17. package/lib/field/CancelAlert/CancelAlert.css +4 -4
  18. package/lib/field/CancelAlert/CancelAlert.tsx +6 -6
  19. package/lib/field/Checkbox/Checkbox.tsx +97 -4
  20. package/lib/field/Currency/Currency.tsx +3 -3
  21. package/lib/field/Currency/currency-utils.ts +1 -2
  22. package/lib/field/Date/Date.tsx +3 -7
  23. package/lib/field/DateTime/DateTime.tsx +3 -8
  24. package/lib/field/Decimal/Decimal.tsx +3 -5
  25. package/lib/field/Dropdown/Dropdown.tsx +5 -7
  26. package/lib/field/Email/Email.tsx +11 -13
  27. package/lib/field/Group/Group.tsx +10 -8
  28. package/lib/field/Integer/Integer.tsx +5 -7
  29. package/lib/field/Location/Location.css +4 -0
  30. package/lib/field/Location/Location.tsx +258 -0
  31. package/lib/field/Location/config-ext.json +8 -0
  32. package/lib/field/Location/index.tsx +1 -0
  33. package/lib/field/Multiselect/utils.ts +1 -1
  34. package/lib/field/ObjectReference/ObjectReference.tsx +235 -0
  35. package/lib/field/ObjectReference/index.tsx +1 -0
  36. package/lib/field/ObjectReference/utils.ts +111 -0
  37. package/lib/field/Percentage/Percentage.tsx +3 -7
  38. package/lib/field/Phone/Phone.tsx +7 -5
  39. package/lib/field/RadioButtons/RadioButtons.tsx +47 -2
  40. package/lib/field/RichText/RichText.css +79 -0
  41. package/lib/field/RichText/RichText.tsx +3 -1
  42. package/lib/field/ScalarList/ScalarList.tsx +2 -3
  43. package/lib/field/SelectableCard/SelectableCard.tsx +189 -0
  44. package/lib/field/SelectableCard/index.tsx +1 -0
  45. package/lib/field/SelectableCard/utils.tsx +223 -0
  46. package/lib/field/SemanticLink/SemanticLink.tsx +160 -28
  47. package/lib/field/SemanticLink/utils.ts +1 -1
  48. package/lib/field/TextArea/TextArea.tsx +5 -7
  49. package/lib/field/TextContent/TextContent.tsx +1 -2
  50. package/lib/field/TextInput/TextInput.tsx +5 -7
  51. package/lib/field/Time/Time.tsx +3 -7
  52. package/lib/field/URL/URL.tsx +5 -7
  53. package/lib/field/UserReference/UserReference.tsx +2 -3
  54. package/lib/helpers/attachmentShared.ts +6 -0
  55. package/lib/helpers/common-utils.ts +3 -4
  56. package/lib/helpers/data_page.ts +0 -1
  57. package/lib/helpers/field-group-utils.ts +1 -1
  58. package/lib/helpers/formatters/Currency.ts +9 -4
  59. package/lib/helpers/formatters/CurrencyMap.ts +0 -2
  60. package/lib/helpers/object-utils.ts +10 -0
  61. package/lib/helpers/simpleTableHelpers.ts +118 -6
  62. package/lib/helpers/utils.ts +8 -1
  63. package/lib/helpers/versionHelpers.ts +0 -1
  64. package/lib/infra/ActionButtons/ActionButtons.tsx +28 -21
  65. package/lib/infra/Assignment/Assignment.tsx +47 -31
  66. package/lib/infra/Assignment/useValidationBanner.ts +29 -0
  67. package/lib/infra/AssignmentCard/AssignmentCard.tsx +2 -2
  68. package/lib/infra/Containers/FlowContainer/FlowContainer.tsx +22 -102
  69. package/lib/infra/Containers/ModalViewContainer/ListViewActionButtons/ListViewActionButtons.tsx +1 -2
  70. package/lib/infra/Containers/ModalViewContainer/ModalViewContainer.tsx +12 -6
  71. package/lib/infra/Containers/ViewContainer/ViewContainer.tsx +8 -13
  72. package/lib/infra/Containers/container-helpers.ts +47 -1
  73. package/lib/infra/DashboardFilter/DashboardFilter.tsx +3 -6
  74. package/lib/infra/DashboardFilter/filterUtils.tsx +3 -4
  75. package/lib/infra/DeferLoad/DeferLoad.tsx +26 -13
  76. package/lib/infra/ErrorBoundary/ErrorBoundary.tsx +1 -3
  77. package/lib/infra/MultiStep/MultiStep.css +48 -70
  78. package/lib/infra/MultiStep/MultiStep.tsx +27 -53
  79. package/lib/infra/NavBar/NavBar.css +1 -1
  80. package/lib/infra/NavBar/NavBar.tsx +49 -34
  81. package/lib/infra/Reference/Reference.tsx +8 -4
  82. package/lib/infra/Region/Region.tsx +1 -1
  83. package/lib/infra/RootContainer/RootContainer.tsx +6 -8
  84. package/lib/infra/Stages/Stages.tsx +3 -4
  85. package/lib/infra/View/View.tsx +9 -9
  86. package/lib/template/AdvancedSearch/AdvancedSearch.tsx +86 -0
  87. package/lib/template/AdvancedSearch/SearchGroup/persistUtils.ts +52 -0
  88. package/lib/template/AdvancedSearch/SearchGroups/SearchGroups.tsx +244 -0
  89. package/lib/template/AdvancedSearch/SearchGroups/hooks.ts +37 -0
  90. package/lib/template/AdvancedSearch/SearchGroups/index.tsx +1 -0
  91. package/lib/template/AdvancedSearch/SearchGroups/utils.ts +29 -0
  92. package/lib/template/AdvancedSearch/TemplateContext.ts +11 -0
  93. package/lib/template/AdvancedSearch/config-ext.json +9 -0
  94. package/lib/template/AdvancedSearch/index.tsx +1 -0
  95. package/lib/template/AppShell/AppShell.css +1 -5
  96. package/lib/template/AppShell/AppShell.tsx +16 -17
  97. package/lib/template/BannerPage/BannerPage.tsx +2 -2
  98. package/lib/template/CaseSummary/CaseSummary.tsx +25 -43
  99. package/lib/template/CaseView/CaseView.tsx +28 -35
  100. package/lib/template/CaseViewActionsMenu/CaseViewActionsMenu.tsx +1 -1
  101. package/lib/template/Confirmation/Confirmation.tsx +2 -3
  102. package/lib/template/DataReference/DataReference.tsx +312 -106
  103. package/lib/template/DataReference/DataReferenceAdvancedSearchContext.ts +10 -0
  104. package/lib/template/DataReference/SearchForm.tsx +149 -0
  105. package/lib/template/DataReference/utils.ts +90 -0
  106. package/lib/template/DefaultForm/DefaultForm.tsx +3 -3
  107. package/lib/template/DefaultForm/utils/index.ts +1 -3
  108. package/lib/template/DefaultPage/DefaultPage.tsx +108 -0
  109. package/lib/template/DefaultPage/index.tsx +1 -0
  110. package/lib/template/Details/Details/Details.tsx +11 -11
  111. package/lib/template/Details/DetailsSubTabs/DetailsSubTabs.tsx +2 -2
  112. package/lib/template/Details/DetailsThreeColumn/DetailsThreeColumn.tsx +11 -11
  113. package/lib/template/Details/DetailsTwoColumn/DetailsTwoColumn.tsx +11 -11
  114. package/lib/template/Details/DynamicTabs/DynamicTabs.tsx +1 -1
  115. package/lib/template/FieldGroupTemplate/FieldGroupTemplate.tsx +12 -6
  116. package/lib/template/HierarchicalForm/HierarchicalForm.tsx +58 -0
  117. package/lib/template/HierarchicalForm/hooks.ts +224 -0
  118. package/lib/template/HierarchicalForm/index.tsx +1 -0
  119. package/lib/template/InlineDashboard/InlineDashboard.tsx +14 -16
  120. package/lib/template/InlineDashboardPage/InlineDashboardPage.tsx +2 -2
  121. package/lib/template/ListPage/ListPage.tsx +1 -1
  122. package/lib/template/ListView/ListView.tsx +342 -204
  123. package/lib/template/ListView/hooks.ts +1 -5
  124. package/lib/template/ListView/utils.ts +38 -5
  125. package/lib/template/MultiReferenceReadOnly/MultiReferenceReadOnly.tsx +17 -2
  126. package/lib/template/NarrowWide/NarrowWide/NarrowWide.tsx +5 -5
  127. package/lib/template/NarrowWide/NarrowWideDetails/NarrowWideDetails.tsx +11 -11
  128. package/lib/template/NarrowWide/NarrowWideForm/NarrowWideForm.tsx +2 -2
  129. package/lib/template/NarrowWide/NarrowWidePage/NarrowWidePage.tsx +2 -2
  130. package/lib/template/ObjectPage/index.tsx +1 -0
  131. package/lib/template/OneColumn/OneColumn/OneColumn.tsx +7 -7
  132. package/lib/template/OneColumn/OneColumnPage/OneColumnPage.tsx +1 -1
  133. package/lib/template/OneColumn/OneColumnTab/OneColumnTab.tsx +2 -2
  134. package/lib/template/PromotedFilters/PromotedFilters.tsx +1 -2
  135. package/lib/template/SelfServiceCaseView/SelfServiceCaseView.tsx +153 -0
  136. package/lib/template/SelfServiceCaseView/index.tsx +1 -0
  137. package/lib/template/SimpleTable/SimpleTable/SimpleTable.tsx +2 -3
  138. package/lib/template/SimpleTable/SimpleTableManual/SimpleTableManual.tsx +45 -34
  139. package/lib/template/SimpleTable/SimpleTableSelect/SimpleTableSelect.tsx +1 -1
  140. package/lib/template/SimpleTable/SimpleTableSelectReadonly/SimpleTableSelectReadonly.tsx +179 -0
  141. package/lib/template/SimpleTable/SimpleTableSelectReadonly/index.tsx +1 -0
  142. package/lib/template/SingleReferenceReadOnly/SingleReferenceReadOnly.tsx +10 -2
  143. package/lib/template/SubTabs/SubTabs.tsx +2 -2
  144. package/lib/template/SubTabs/tabUtils.ts +118 -1
  145. package/lib/template/TwoColumn/TwoColumn/TwoColumn.tsx +9 -10
  146. package/lib/template/TwoColumn/TwoColumnPage/TwoColumnPage.tsx +1 -1
  147. package/lib/template/TwoColumn/TwoColumnTab/TwoColumnTab.tsx +9 -10
  148. package/lib/template/WideNarrow/WideNarrow/WideNarrow.tsx +5 -5
  149. package/lib/template/WideNarrow/WideNarrowDetails/WideNarrowDetails.tsx +11 -11
  150. package/lib/template/WideNarrow/WideNarrowForm/WideNarrowForm.tsx +2 -2
  151. package/lib/template/WideNarrow/WideNarrowPage/WideNarrowPage.tsx +2 -2
  152. package/lib/template/WssNavBar/WssNavBar.css +1 -1
  153. package/lib/template/WssNavBar/WssNavBar.tsx +6 -6
  154. package/lib/template/utils.tsx +58 -0
  155. package/lib/widget/AppAnnouncement/AppAnnouncement.tsx +1 -1
  156. package/lib/widget/Attachment/Attachment.css +6 -8
  157. package/lib/widget/Attachment/Attachment.tsx +303 -225
  158. package/lib/widget/Attachment/Attachment.types.ts +96 -0
  159. package/lib/widget/Attachment/AttachmentUtils.ts +316 -0
  160. package/lib/widget/CaseHistory/CaseHistory.tsx +5 -5
  161. package/lib/widget/FileUtility/ActionButtonsForFileUtil/ActionButtonsForFileUtil.css +0 -14
  162. package/lib/widget/FileUtility/ActionButtonsForFileUtil/ActionButtonsForFileUtil.tsx +3 -3
  163. package/lib/widget/FileUtility/FileUtility/FileUtility.css +7 -6
  164. package/lib/widget/FileUtility/FileUtility/FileUtility.tsx +29 -22
  165. package/lib/widget/Followers/Followers.tsx +2 -4
  166. package/lib/widget/QuickCreate/QuickCreate.tsx +1 -2
  167. package/lib/widget/SummaryItem/SummaryItem.css +9 -11
  168. package/lib/widget/SummaryItem/SummaryItem.tsx +2 -2
  169. package/lib/widget/SummaryList/SummaryList.tsx +1 -1
  170. package/lib/widget/ToDo/ToDo.css +1 -13
  171. package/lib/widget/ToDo/ToDo.tsx +37 -36
  172. package/package.json +1 -1
  173. package/lib/helpers/attachmentHelpers.ts +0 -76
@@ -1,173 +1,192 @@
1
- /* eslint-disable react/jsx-boolean-value */
2
- /* eslint-disable react/no-array-index-key */
3
- /* eslint-disable no-nested-ternary */
4
- import { useState, useEffect, useCallback } from 'react';
1
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
5
2
  import { CircularProgress, IconButton, Menu, MenuItem, Button } from '@mui/material';
6
3
  import MoreVertIcon from '@mui/icons-material/MoreVert';
7
- import download from 'downloadjs';
8
4
 
9
- import { buildFilePropsFromResponse, getIconFromFileType, validateMaxSize } from '@pega/react-sdk-components/lib/components/helpers/attachmentHelpers';
10
5
  import { Utils } from '@pega/react-sdk-components/lib/components/helpers/utils';
11
- import { PConnFieldProps } from '@pega/react-sdk-components/lib/types/PConnProps';
6
+ import {
7
+ clearFieldErrorMessages,
8
+ deleteAttachments,
9
+ getIconFromFileType,
10
+ getMappedValue,
11
+ insertAttachments,
12
+ useDeepMemo,
13
+ useFileDownload,
14
+ validateFileExtension
15
+ } from './AttachmentUtils';
16
+ import { validateMaxSize } from '@pega/react-sdk-components/lib/components/helpers/attachmentShared';
17
+ import type { PageInstructionOptions } from './Attachment.types';
18
+ import type { PConnFieldProps } from '@pega/react-sdk-components/lib/types/PConnProps';
12
19
 
13
20
  import './Attachment.css';
14
21
 
15
22
  interface AttachmentProps extends Omit<PConnFieldProps, 'value'> {
16
23
  // If any, enter additional props that only exist on this component
17
24
  value: any;
18
- allowMultiple: string;
25
+ allowMultiple: boolean | string;
19
26
  extensions: string;
27
+ editMode: string;
28
+ isTableFormatter: boolean;
20
29
  }
21
30
 
22
- const getAttachmentKey = (name = '') => (name ? `attachmentsList.${name}` : 'attachmentsList');
23
-
24
- const getCurrentAttachmentsList = (key, context) => {
25
- return PCore.getStoreValue(`.${key}`, 'context_data', context) || [];
26
- };
27
-
28
- const updateAttachmentState = (pConn, key, attachments) => {
29
- PCore.getStateUtils().updateState(pConn.getContextName(), key, attachments, {
30
- pageReference: 'context_data',
31
- isArrayDeepMerge: false
32
- });
33
- };
34
-
35
31
  export default function Attachment(props: AttachmentProps) {
36
- const { value, getPConnect, label, validatemessage, allowMultiple, extensions, displayMode, helperText } = props;
32
+ const { value, getPConnect, label, validatemessage, extensions, displayMode, helperText, editMode, isTableFormatter } = props;
37
33
  /* this is a temporary fix because required is supposed to be passed as a boolean and NOT as a string */
38
- let { required, disabled } = props;
39
- [required, disabled] = [required, disabled].map(prop => prop === true || (typeof prop === 'string' && prop === 'true'));
34
+ let { required, disabled, allowMultiple } = props;
35
+ [required, disabled, allowMultiple] = [required, disabled, allowMultiple].map(
36
+ prop => prop === true || (typeof prop === 'string' && prop === 'true')
37
+ );
40
38
  const pConn = getPConnect();
41
- const caseID = PCore.getStoreValue('.pyID', 'caseInfo.content', pConn.getContextName());
39
+ const localizationService = pConn.getLocalizationService();
40
+
41
+ const actionSequencer = useMemo(() => PCore.getActionsSequencer(), []);
42
+ const rawValue = pConn.getComponentConfig().value;
43
+ const isAttachmentAnnotationPresent = typeof rawValue === 'object' ? false : rawValue?.includes('@ATTACHMENT');
44
+ const { attachments, isOldAttachment } = isAttachmentAnnotationPresent ? value : PCore.getAttachmentUtils().prepareAttachmentData(value);
45
+
46
+ let valueRef = (pConn.getStateProps() as any).value;
47
+ valueRef = valueRef.indexOf('.') === 0 ? valueRef.substring(1) : valueRef;
48
+
49
+ pConn.setReferenceList(`.${valueRef}`);
50
+
51
+ const isMultiAttachmentInInlineEditTable = isTableFormatter && allowMultiple && editMode === 'tableRows';
52
+
53
+ const [files, setFiles] = useState<any[]>(attachments);
54
+ const overrideLocalState = useRef(false);
55
+ const attachmentCount = useRef(attachments.length);
56
+ const filesWithError = useRef<any[]>([]);
57
+ const multiAttachmentsInInlineEdit = useRef([]);
58
+ const thumbnailURLs = useRef<any[]>([]);
59
+ const contextName = pConn.getContextName();
60
+ const onFileDownload = useFileDownload(contextName);
61
+
42
62
  const localizedVal = PCore.getLocaleUtils().getLocaleValue;
43
63
  const localeCategory = 'CosmosFields';
44
64
  const uploadMultipleFilesLabel = localizedVal('file_upload_text_multiple', localeCategory);
45
65
  const uploadSingleFileLabel = localizedVal('file_upload_text_one', localeCategory);
46
- let categoryName = '';
47
- if (value && value.pyCategoryName) {
48
- categoryName = value.pyCategoryName;
49
- }
50
66
  const deleteIcon = Utils.getImageSrc('trash', Utils.getSDKStaticConentUrl());
51
67
  const srcImg = Utils.getImageSrc('document-doc', Utils.getSDKStaticConentUrl());
52
- let valueRef = (pConn.getStateProps() as any).value;
53
- valueRef = valueRef.indexOf('.') === 0 ? valueRef.substring(1) : valueRef;
68
+
54
69
  const [anchorEl, setAnchorEl] = useState(null);
55
70
  const open = Boolean(anchorEl);
56
- const [files, setFiles] = useState<any[]>(() =>
57
- value?.pxResults && +value.pyCount > 0 ? value.pxResults.map(f => buildFilePropsFromResponse(f)) : []
58
- );
59
- const [filesWithError, setFilesWithError] = useState<any[]>([]);
71
+
72
+ const fileInputRef = useRef<HTMLInputElement>(null);
60
73
  const [toggleUploadBegin, setToggleUploadBegin] = useState(false);
61
74
 
62
- const resetAttachmentStoredState = () => {
63
- PCore.getStateUtils().updateState(pConn.getContextName(), getAttachmentKey(valueRef), undefined, {
64
- pageReference: 'context_data',
65
- isArrayDeepMerge: false
66
- });
67
- };
75
+ const deleteFile = useCallback(
76
+ (file, fileIndex) => {
77
+ setAnchorEl(null);
68
78
 
69
- const fileDownload = (data, fileName, ext) => {
70
- const fileData = ext ? `${fileName}.${ext}` : fileName;
71
- download(atob(data), fileData);
72
- };
79
+ // reset the file input so that it will allow re-uploading the same file after deletion
80
+ if (fileInputRef.current) {
81
+ fileInputRef.current.value = ''; // Reset the input
82
+ }
73
83
 
74
- const downloadFile = (fileObj: any) => {
75
- setAnchorEl(null);
76
- PCore.getAttachmentUtils()
77
- // @ts-ignore - 3rd parameter "responseEncoding" should be optional
78
- .downloadAttachment(fileObj.pzInsKey, pConn.getContextName())
79
- .then((content: any) => {
80
- const extension = fileObj.pyAttachName.split('.').pop();
81
- fileDownload(content.data, fileObj.pyFileName, extension);
82
- })
83
- .catch(() => {});
84
- };
84
+ if (filesWithError.current.length > 0) {
85
+ filesWithError.current = filesWithError.current.filter(fileWithError => fileWithError.props.id !== file.props.id);
86
+ if (filesWithError.current.length === 0) {
87
+ clearFieldErrorMessages(pConn);
88
+ }
89
+ }
85
90
 
86
- const deleteFile = useCallback(
87
- file => {
88
- setAnchorEl(null);
89
- let attachmentsList: any[] = [];
90
- let currentAttachmentList = getCurrentAttachmentsList(getAttachmentKey(valueRef), pConn.getContextName());
91
-
92
- // If file to be deleted is the one added in previous stage i.e. for which a file instance is created in server
93
- // no need to filter currentAttachmentList as we will get another entry of file in redux with delete & label
94
- // eslint-disable-next-line no-unsafe-optional-chaining
95
- if (value && value?.pxResults && +value?.pyCount > 0 && file.responseProps && file?.responseProps?.pzInsKey !== 'temp') {
96
- const updatedAttachments = files.map(f => {
97
- if (f.responseProps && f.responseProps.pzInsKey === file.responseProps.pzInsKey) {
98
- return { ...f, delete: true, label: valueRef };
99
- }
100
- return f;
91
+ if (file.inProgress) {
92
+ // @ts-ignore - Expected 1 arguments, but got 2.ts(2554)
93
+ PCore.getAttachmentUtils().cancelRequest(file.props.id, contextName);
94
+ actionSequencer.deRegisterBlockingAction(contextName).catch(() => {});
95
+ setFiles(localFiles => {
96
+ return localFiles.filter(localFile => localFile.props.id !== file.props.id);
101
97
  });
98
+ } else {
99
+ deleteAttachments([file], pConn, multiAttachmentsInInlineEdit.current, {
100
+ allowMultiple,
101
+ isOldAttachment,
102
+ isMultiAttachmentInInlineEditTable,
103
+ attachmentCount: attachmentCount.current,
104
+ deleteIndex: fileIndex
105
+ } as any);
106
+ // Filter out without deleted file and reset the file indexes
107
+ setFiles(localFiles => {
108
+ let tempLocalFiles = [...localFiles];
109
+ tempLocalFiles = tempLocalFiles.filter(localFile => localFile.props.id !== file.props.id);
110
+ tempLocalFiles.forEach(localFile => {
111
+ if (!localFile.props.error && !file.props.error) {
112
+ const updatedDeleteIndex =
113
+ localFile.responseProps.deleteIndex > fileIndex ? localFile.responseProps.deleteIndex - 1 : localFile.responseProps.deleteIndex;
102
114
 
103
- // updating the redux store to help form-handler in passing the data to delete the file from server
104
- updateAttachmentState(pConn, getAttachmentKey(valueRef), [...updatedAttachments]);
105
- setFiles(current => {
106
- const newlyAddedFiles = current.filter(f => !!f.ID);
107
- const filesPostDelete = current.filter(
108
- f => f.responseProps?.pzInsKey !== 'temp' && f.responseProps?.pzInsKey !== file.responseProps?.pzInsKey
109
- );
110
- attachmentsList = [...filesPostDelete, ...newlyAddedFiles];
111
- return attachmentsList;
112
- });
113
- } // if the file being deleted is the added in this stage i.e. whose data is not yet created in server
114
- else {
115
- // filter newly added files in this stage, later the updated current stage files will be added to redux once files state is updated in below setFiles()
116
- currentAttachmentList = currentAttachmentList.filter(f => f.label !== valueRef);
117
- setFiles(current => {
118
- attachmentsList = current.filter(f => f.ID !== file.ID);
119
- return attachmentsList;
115
+ localFile.props.onDelete = () => deleteFile(localFile, updatedDeleteIndex);
116
+
117
+ localFile.responseProps.deleteIndex = updatedDeleteIndex;
118
+ }
119
+ });
120
+ return tempLocalFiles;
120
121
  });
121
- updateAttachmentState(pConn, getAttachmentKey(valueRef), [...currentAttachmentList, ...attachmentsList]);
122
- if (file.inProgress) {
123
- // @ts-ignore - 3rd parameter "responseEncoding" should be optional
124
- PCore.getAttachmentUtils().cancelRequest(file.ID, pConn.getContextName());
122
+ if (!file.props.error) {
123
+ attachmentCount.current -= 1;
125
124
  }
126
125
  }
127
126
 
128
127
  setToggleUploadBegin(false);
129
- setFilesWithError(prevFilesWithError => {
130
- return prevFilesWithError.filter(f => f.ID !== file.ID);
131
- });
132
128
  },
133
- [pConn, value, valueRef, filesWithError]
129
+ [pConn]
134
130
  );
135
131
 
136
- const onUploadProgress = () => {};
132
+ const onUploadProgress = (id, ev) => {
133
+ const progress = Math.floor((ev.loaded / ev.total) * 100);
134
+ setFiles(localFiles => [
135
+ ...localFiles.map(localFile => {
136
+ if (localFile.props?.id === id) {
137
+ localFile.inProgress = true;
138
+ localFile.props.progress = progress;
139
+ }
140
+ return localFile;
141
+ })
142
+ ]);
143
+ };
137
144
 
138
- const errorHandler = (isFetchCanceled, attachedFile) => {
145
+ const populateErrorAndUpdateRedux = file => {
146
+ const fieldName = (pConn.getStateProps() as any).value;
147
+ // set errors to property to block submit even on errors in file upload
148
+ PCore.getMessageManager().addMessages({
149
+ messages: [
150
+ {
151
+ type: 'error',
152
+ message: localizationService.getLocalizedText('Error with one or more files')
153
+ }
154
+ ],
155
+ property: fieldName,
156
+ pageReference: pConn.getPageReference(),
157
+ context: contextName
158
+ });
159
+ insertAttachments([file], pConn, multiAttachmentsInInlineEdit.current, {
160
+ allowMultiple,
161
+ isOldAttachment,
162
+ isMultiAttachmentInInlineEditTable,
163
+ attachmentCount: attachmentCount.current
164
+ } as any);
165
+ };
166
+
167
+ const errorHandler = (isFetchCanceled, file) => {
139
168
  return error => {
140
169
  if (!isFetchCanceled(error)) {
141
- let uploadFailMsg = pConn.getLocalizedValue('Something went wrong', '', '');
170
+ let uploadFailMsg = localizationService.getLocalizedText('Something went wrong');
142
171
  if (error.response && error.response.data && error.response.data.errorDetails) {
143
- uploadFailMsg = pConn.getLocalizedValue(error.response.data.errorDetails[0].localizedValue, '', '');
172
+ uploadFailMsg = localizationService.getLocalizedText(error.response.data.errorDetails[0].localizedValue);
144
173
  }
174
+
145
175
  setFiles(current => {
146
- return current.map(f => {
147
- if (f.ID === attachedFile.ID) {
148
- f.props.meta = uploadFailMsg;
149
- f.props.error = true;
150
- f.props.onDelete = () => deleteFile(f);
151
- f.props.icon = getIconFromFileType(f.type);
152
- f.props.name = pConn.getLocalizedValue('Unable to upload file', '', '');
153
- f.inProgress = false;
154
- const fieldName = (pConn.getStateProps() as any).value;
155
- const context = pConn.getContextName();
156
- // set errors to property to block submit even on errors in file upload
157
- PCore.getMessageManager().addMessages({
158
- messages: [
159
- {
160
- type: 'error',
161
- message: pConn.getLocalizedValue('Error with one or more files', '', '')
162
- }
163
- ],
164
- property: fieldName,
165
- pageReference: pConn.getPageReference(),
166
- context
167
- });
168
- delete f.props.progress;
176
+ return current.map((localFile, index) => {
177
+ if (localFile.props.id === file.props.id) {
178
+ localFile.props.meta = uploadFailMsg;
179
+ localFile.props.error = true;
180
+ localFile.props.onDelete = () => deleteFile(localFile, index);
181
+ localFile.props.icon = getIconFromFileType(localFile.type);
182
+ localFile.props.name = localizationService.getLocalizedText('Unable to upload file');
183
+ localFile.inProgress = false;
184
+ delete localFile.props.progress;
185
+ filesWithError.current.push(localFile);
186
+
187
+ populateErrorAndUpdateRedux(localFile);
169
188
  }
170
- return f;
189
+ return localFile;
171
190
  });
172
191
  });
173
192
  }
@@ -175,78 +194,62 @@ export default function Attachment(props: AttachmentProps) {
175
194
  };
176
195
  };
177
196
 
178
- const validateFileExtension = (fileObj, allowedExtensions) => {
179
- if (!allowedExtensions) {
180
- return true;
181
- }
182
- const allowedExtensionList = allowedExtensions
183
- .toLowerCase()
184
- .split(',')
185
- .map(item => item.replaceAll('.', '').trim());
186
- const extension = fileObj.name.split('.').pop().toLowerCase();
187
- return allowedExtensionList.includes(extension);
188
- };
189
-
190
- const clearFieldErrorMessages = () => {
191
- const fieldName = (pConn.getStateProps() as any).value;
192
- const context = pConn.getContextName();
193
- PCore.getMessageManager().clearMessages({
194
- type: PCore.getConstants().MESSAGES.MESSAGES_TYPE_ERROR,
195
- property: fieldName,
196
- pageReference: pConn.getPageReference(),
197
- context
198
- });
199
- };
200
-
201
197
  const onFileAdded = event => {
202
198
  let addedFiles = Array.from(event.target.files);
203
- addedFiles = allowMultiple === 'true' ? addedFiles : [addedFiles[0]];
199
+ addedFiles = allowMultiple ? addedFiles : [addedFiles[0]];
204
200
  const maxAttachmentSize = PCore.getEnvironmentInfo().getMaxAttachmentSize() || '5';
205
201
  const tempFilesToBeUploaded = [
206
202
  ...addedFiles.map((f: any, index) => {
207
203
  f.ID = `${new Date().getTime()}I${index}`;
208
- f.inProgress = true;
209
204
  f.props = {
210
205
  type: f.type,
211
206
  name: f.name,
207
+ id: f.ID,
208
+ format: f.name.split('.').pop(),
212
209
  icon: getIconFromFileType(f.type),
213
- onDelete: () => deleteFile(f)
210
+ onDelete: () => deleteFile(f, index),
211
+ thumbnail: window.URL.createObjectURL(f)
214
212
  };
215
213
  if (!validateMaxSize(f, maxAttachmentSize)) {
216
214
  f.props.error = true;
217
- f.inProgress = false;
218
- f.props.meta = pConn.getLocalizedValue(`File is too big. Max allowed size is ${maxAttachmentSize}MB.`, '', '');
215
+ f.props.meta = localizationService.getLocalizedText(`File is too big. Max allowed size is ${maxAttachmentSize}MB.`);
219
216
  } else if (!validateFileExtension(f, extensions)) {
220
217
  f.props.error = true;
221
- f.inProgress = false;
222
- f.props.meta = `${pConn.getLocalizedValue('File has invalid extension. Allowed extensions are:', '', '')} ${extensions.replaceAll(
218
+ f.props.meta = `${localizationService.getLocalizedText('File has invalid extension. Allowed extensions are:')} ${extensions.replaceAll(
223
219
  '.',
224
220
  ''
225
221
  )}`;
226
222
  }
227
223
  if (f.props.error) {
228
224
  const fieldName = (pConn.getStateProps() as any).value;
229
- const context = pConn.getContextName();
230
225
  PCore.getMessageManager().addMessages({
231
226
  messages: [
232
227
  {
233
228
  type: 'error',
234
- message: pConn.getLocalizedValue('Error with one or more files', '', '')
229
+ message: localizationService.getLocalizedText('Error with one or more files')
235
230
  }
236
231
  ],
237
232
  property: fieldName,
238
233
  pageReference: pConn.getPageReference(),
239
- context
234
+ context: contextName
240
235
  });
241
236
  }
242
237
  return f;
243
238
  })
244
239
  ];
240
+
245
241
  const tempFilesWithError = tempFilesToBeUploaded.filter(f => f.props.error);
246
242
  if (tempFilesWithError.length > 0) {
247
- setFilesWithError(tempFilesWithError);
243
+ filesWithError.current = [...filesWithError.current, ...tempFilesWithError];
244
+
245
+ insertAttachments(tempFilesWithError, pConn, multiAttachmentsInInlineEdit.current, {
246
+ allowMultiple,
247
+ isOldAttachment,
248
+ isMultiAttachmentInInlineEditTable,
249
+ attachmentCount: attachmentCount.current
250
+ } as PageInstructionOptions);
248
251
  }
249
- setFiles(current => (allowMultiple !== 'true' ? [...tempFilesToBeUploaded] : [...current, ...tempFilesToBeUploaded]));
252
+ setFiles(current => (!allowMultiple ? [...tempFilesToBeUploaded] : [...current, ...tempFilesToBeUploaded]));
250
253
  setToggleUploadBegin(true);
251
254
  };
252
255
 
@@ -255,106 +258,178 @@ export default function Attachment(props: AttachmentProps) {
255
258
  .filter(e => {
256
259
  const isFileUploaded = e.props && e.props.progress === 100;
257
260
  const fileHasError = e.props && e.props.error;
258
- const isFileUploadedinLastStep = e.responseProps && e.responseProps.pzInsKey;
259
- return !isFileUploaded && !fileHasError && !isFileUploadedinLastStep;
261
+ const isFileUploadedInLastStep = e.responseProps && e.responseProps.ID !== 'temp';
262
+ const isFileUploadInProgress = e.inProgress;
263
+ return !isFileUploadInProgress && !isFileUploaded && !fileHasError && !isFileUploadedInLastStep;
260
264
  })
261
- .map(f =>
262
- window.PCore.getAttachmentUtils().uploadAttachment(
263
- f,
264
- () => {
265
- onUploadProgress();
265
+ .map(file =>
266
+ PCore.getAttachmentUtils().uploadAttachment(
267
+ file,
268
+ ev => {
269
+ onUploadProgress(file.props.id, ev);
266
270
  },
267
271
  isFetchCanceled => {
268
- return errorHandler(isFetchCanceled, f);
272
+ return errorHandler(isFetchCanceled, file);
269
273
  },
270
- pConn.getContextName()
274
+ contextName
271
275
  )
272
276
  );
277
+
278
+ // allow new files to be added when other files upload is still in progress
279
+ setToggleUploadBegin(false);
273
280
  Promise.allSettled(filesToBeUploaded)
274
281
  .then((fileResponses: any) => {
275
282
  fileResponses = fileResponses.filter(fr => fr.status !== 'rejected'); // in case of deleting an in progress file, promise gets cancelled but still enters then block
276
283
  if (fileResponses.length > 0) {
277
- setFiles(current => {
278
- const tempFilesUploaded = [...current];
279
- tempFilesUploaded.forEach(f => {
280
- const index = fileResponses.findIndex((fr: any) => fr.value.clientFileID === f.ID);
284
+ setFiles(localFiles => {
285
+ const tempFilesUploaded = [...localFiles];
286
+ tempFilesUploaded.forEach(localFile => {
287
+ // if attach field has multiple files & in bw any error files are present
288
+ // Example : files = [properFile1, errFile, errFile, properFile2]
289
+ // indexes for delete & preview should be for files [properFile1, properFile2] which is [1,2]
290
+ const index = fileResponses.findIndex(fileResponse => fileResponse.value.clientFileID === localFile.props.id);
281
291
  if (index >= 0) {
282
- f.props.meta = pConn.getLocalizedValue('Uploaded successfully', '', '');
283
- f.props.progress = 100;
284
- f.inProgress = false;
285
- f.handle = fileResponses[index].value.ID;
286
- f.label = valueRef;
287
- f.category = categoryName;
288
- f.responseProps = {
289
- pzInsKey: 'temp',
290
- pyAttachName: f.props.name
292
+ fileResponses[index].value.thumbnail = localFile.props.thumbnail;
293
+ localFile.inProgress = false;
294
+ localFile.ID = fileResponses[index].value.ID;
295
+ localFile.props.meta = localizationService.getLocalizedText('Uploaded successfully');
296
+ localFile.props.progress = 100;
297
+ localFile.handle = fileResponses[index].value.ID;
298
+ localFile.label = valueRef;
299
+ localFile.responseProps = {
300
+ pzInsKey: 'temp'
291
301
  };
292
302
  }
293
303
  });
294
304
  return tempFilesUploaded;
295
305
  });
296
306
 
297
- if (filesWithError.length === 0) {
298
- clearFieldErrorMessages();
307
+ insertAttachments(fileResponses, pConn, multiAttachmentsInInlineEdit.current, {
308
+ allowMultiple,
309
+ isOldAttachment,
310
+ isMultiAttachmentInInlineEditTable,
311
+ attachmentCount: attachmentCount.current,
312
+ insert: true
313
+ } as any);
314
+ attachmentCount.current += fileResponses.length;
315
+
316
+ if (filesWithError.current.length === 0) {
317
+ clearFieldErrorMessages(pConn);
299
318
  }
300
319
  }
301
- setToggleUploadBegin(false);
320
+ actionSequencer.deRegisterBlockingAction(contextName).catch(() => {});
302
321
  })
303
322
  .catch(error => {
304
- // eslint-disable-next-line no-console
305
323
  console.log(error);
306
324
  setToggleUploadBegin(false);
307
325
  });
308
- }, [files, filesWithError]);
326
+ }, [files]);
309
327
 
310
328
  useEffect(() => {
311
329
  if (toggleUploadBegin && files.length > 0) {
312
- uploadFiles();
330
+ actionSequencer.registerBlockingAction(contextName).then(() => {
331
+ uploadFiles();
332
+ });
313
333
  }
314
334
  }, [toggleUploadBegin]);
315
335
 
316
336
  useEffect(() => {
317
- if (files.length > 0 && displayMode !== 'DISPLAY_ONLY') {
318
- const currentAttachmentList = getCurrentAttachmentsList(getAttachmentKey(valueRef), pConn.getContextName());
319
- // block duplicate files to redux store when added 1 after another to prevent multiple duplicates being added to the case on submit
320
- const tempFiles = files.filter(f => currentAttachmentList.findIndex(fr => fr.ID === f.ID) === -1 && !f.inProgress && f.responseProps);
321
-
322
- const updatedAttList = [...currentAttachmentList, ...tempFiles];
323
- updateAttachmentState(pConn, getAttachmentKey(valueRef), updatedAttList);
337
+ if (filesWithError.current.length === 0) {
338
+ clearFieldErrorMessages(pConn);
324
339
  }
325
- }, [files]);
340
+ }, [filesWithError]);
341
+
342
+ const memoizedAttachments = useDeepMemo(() => {
343
+ return attachments;
344
+ }, [attachments]);
345
+
346
+ // Prepares new structure as per Cosmos component
347
+ const transformAttachments = () => {
348
+ const transformedFiles = [...attachments];
349
+ let deleteIndex = -1;
350
+ transformedFiles.forEach(attachment => {
351
+ attachment.props.id = attachment.responseProps.ID;
352
+ attachment.props.format = attachment.props.name.split('.').pop();
353
+ if (attachment.props.error) {
354
+ attachment.responseProps.deleteIndex = deleteIndex;
355
+ } else {
356
+ deleteIndex += 1;
357
+ attachment.responseProps.deleteIndex = deleteIndex;
358
+ }
359
+ if (attachment.props.thumbnail) {
360
+ thumbnailURLs.current.push(attachment.props.thumbnail);
361
+ }
362
+ });
363
+
364
+ return transformedFiles;
365
+ };
326
366
 
327
367
  useEffect(() => {
328
- if (filesWithError.length === 0) {
329
- clearFieldErrorMessages();
368
+ const caseID = PCore.getStoreValue(`.${getMappedValue('pyID')}`, PCore.getResolvedConstantValue('caseInfo.content'), contextName);
369
+ if (displayMode !== 'DISPLAY_ONLY') {
370
+ PCore.getPubSubUtils().subscribe(
371
+ PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION,
372
+ () => {
373
+ overrideLocalState.current = true;
374
+ },
375
+ caseID
376
+ );
330
377
  }
331
- }, [filesWithError]);
332
378
 
333
- useEffect(() => {
334
- let tempUploadedFiles = getCurrentAttachmentsList(getAttachmentKey(valueRef), pConn.getContextName());
335
- tempUploadedFiles = tempUploadedFiles.filter(f => f.label === valueRef);
336
- setFiles(current => {
337
- return [
338
- ...current.map(f => {
339
- return f.responseProps.pzInsKey && !f.responseProps.pzInsKey.includes('temp')
340
- ? {
341
- ...f,
342
- props: {
343
- ...f.props,
344
- onDelete: () => deleteFile(f)
345
- }
346
- }
347
- : { ...f };
348
- }),
349
- ...tempUploadedFiles
350
- ];
351
- });
352
- PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION, resetAttachmentStoredState, caseID);
379
+ // When component mounts, only set local files state from redux.
380
+ const serverFiles = transformAttachments();
381
+ setFiles(serverFiles);
382
+ filesWithError.current = serverFiles.filter(file => file.props.error);
383
+
353
384
  return () => {
354
- PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION, caseID);
385
+ if (displayMode !== 'DISPLAY_ONLY') {
386
+ PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION, caseID);
387
+ }
355
388
  };
356
389
  }, []);
357
390
 
391
+ useEffect(() => {
392
+ if (overrideLocalState.current) {
393
+ const serverFiles = transformAttachments();
394
+ overrideLocalState.current = false;
395
+ attachmentCount.current = attachments.length;
396
+ filesWithError.current = [];
397
+ setFiles(serverFiles);
398
+ } else {
399
+ // Determine whether refresh call has overridden any error files in redux, push error files back to redux from local state to perform client side validation during assignment submit
400
+ const errorFiles = attachments.filter(attachment => attachment.props.error);
401
+ if (errorFiles.length === 0 && filesWithError.current.length > 0) {
402
+ // Check if local file state contains error files and push those to redux
403
+ const uniqueKey = getMappedValue('pzInsKey');
404
+ const transformedErrorFiles = filesWithError.current.map(errorFile => {
405
+ const filename = errorFile.props.name;
406
+ return {
407
+ [uniqueKey]: errorFile.props.id,
408
+ FileName: filename,
409
+ Category: '',
410
+ FileExtension: filename.split('.').pop() ?? filename,
411
+ error: errorFile.props.error || null
412
+ };
413
+ });
414
+ let key = '';
415
+ let updatedAttachments: any = [];
416
+ if (allowMultiple || isOldAttachment) {
417
+ key = isOldAttachment ? `${valueRef}.pxResults` : valueRef;
418
+ const existingAttachments = PCore.getStoreValue(`.${key}`, pConn.getPageReference(), pConn.getContextName()) || [];
419
+ updatedAttachments = [...existingAttachments, ...transformedErrorFiles];
420
+ } else {
421
+ key = valueRef;
422
+ updatedAttachments = transformedErrorFiles[0];
423
+ }
424
+ PCore.getStateUtils().updateState(pConn.getContextName(), key, updatedAttachments, {
425
+ pageReference: pConn.getPageReference(),
426
+ isArrayDeepMerge: false,
427
+ removePropertyFromChangedList: true
428
+ });
429
+ }
430
+ }
431
+ }, [memoizedAttachments]);
432
+
358
433
  const handleClick = event => {
359
434
  setAnchorEl(event.currentTarget);
360
435
  };
@@ -375,13 +450,13 @@ export default function Attachment(props: AttachmentProps) {
375
450
  id={valueRef}
376
451
  name='upload-photo'
377
452
  type='file'
378
- multiple={allowMultiple === 'true'}
453
+ multiple={allowMultiple}
379
454
  required={required}
380
455
  disabled={disabled}
381
456
  onChange={onFileAdded}
382
457
  />
383
458
  <Button style={{ textTransform: 'none' }} variant='outlined' color='primary' component='span'>
384
- {allowMultiple === 'true'
459
+ {allowMultiple
385
460
  ? uploadMultipleFilesLabel === 'file_upload_text_multiple'
386
461
  ? 'Choose files'
387
462
  : uploadMultipleFilesLabel
@@ -415,7 +490,7 @@ export default function Attachment(props: AttachmentProps) {
415
490
  </div>
416
491
  <div className='psdk-utility-action'>
417
492
  {item.ID && (
418
- <button type='button' className='psdk-utility-button' aria-label='Delete Attachment' onClick={() => deleteFile(item)}>
493
+ <button type='button' className='psdk-utility-button' aria-label='Delete Attachment' onClick={() => deleteFile(item, index)}>
419
494
  <img className='psdk-utility-card-action-svg-icon' src={deleteIcon} />
420
495
  </button>
421
496
  )}
@@ -435,11 +510,14 @@ export default function Attachment(props: AttachmentProps) {
435
510
  <MenuItem
436
511
  style={{ fontSize: '14px' }}
437
512
  key='download'
438
- onClick={() => downloadFile(item.responseProps ? item.responseProps : {})}
513
+ onClick={() => {
514
+ setAnchorEl(null);
515
+ onFileDownload(item.responseProps ? item.responseProps : {});
516
+ }}
439
517
  >
440
518
  Download
441
519
  </MenuItem>
442
- <MenuItem style={{ fontSize: '14px' }} key='delete' onClick={() => deleteFile(item)}>
520
+ <MenuItem style={{ fontSize: '14px' }} key='delete' onClick={() => deleteFile(item, index)}>
443
521
  Delete
444
522
  </MenuItem>
445
523
  </Menu>
@@ -455,7 +533,7 @@ export default function Attachment(props: AttachmentProps) {
455
533
  return (
456
534
  <div className='file-upload-container'>
457
535
  <span className={`label ${required ? 'file-label' : ''}`}>{label}</span>
458
- {((files.length === 0 && allowMultiple !== 'true') || allowMultiple === 'true') && <section>{content}</section>}
536
+ {((files.length === 0 && !allowMultiple) || allowMultiple) && <section>{content}</section>}
459
537
  {validatemessage !== '' ? <span className='file-error'>{validatemessage}</span> : <span style={{ fontSize: '14px' }}>{helperText}</span>}
460
538
  {files && files.length > 0 && <section>{fileDisplay}</section>}
461
539
  </div>