@pega/react-sdk-overrides 24.2.10 → 25.1.10

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 (169) hide show
  1. package/lib/designSystemExtension/Banner/Banner.css +1 -1
  2. package/lib/designSystemExtension/Banner/Banner.tsx +10 -7
  3. package/lib/designSystemExtension/CaseSummaryFields/CaseSummaryFields.css +0 -1
  4. package/lib/designSystemExtension/CaseSummaryFields/CaseSummaryFields.tsx +53 -37
  5. package/lib/designSystemExtension/DetailsFields/DetailsFields.tsx +11 -13
  6. package/lib/designSystemExtension/FieldGroup/FieldGroup.tsx +8 -9
  7. package/lib/designSystemExtension/FieldGroupList/FieldGroupList.tsx +9 -9
  8. package/lib/designSystemExtension/FieldValueList/FieldValueList.tsx +8 -9
  9. package/lib/designSystemExtension/Operator/Operator.tsx +21 -19
  10. package/lib/designSystemExtension/Pulse/Pulse.tsx +1 -1
  11. package/lib/designSystemExtension/RichTextEditor/RichTextEditor.tsx +32 -4
  12. package/lib/designSystemExtension/WssQuickCreate/WssQuickCreate.css +7 -14
  13. package/lib/designSystemExtension/WssQuickCreate/WssQuickCreate.tsx +13 -2
  14. package/lib/field/AutoComplete/AutoComplete.tsx +1 -1
  15. package/lib/field/CancelAlert/CancelAlert.css +4 -4
  16. package/lib/field/CancelAlert/CancelAlert.tsx +7 -10
  17. package/lib/field/Checkbox/Checkbox.tsx +97 -4
  18. package/lib/field/Currency/Currency.tsx +10 -7
  19. package/lib/field/Currency/currency-utils.ts +1 -2
  20. package/lib/field/Date/Date.tsx +8 -8
  21. package/lib/field/DateTime/DateTime.tsx +16 -13
  22. package/lib/field/Decimal/Decimal.tsx +10 -7
  23. package/lib/field/Dropdown/Dropdown.tsx +30 -25
  24. package/lib/field/Email/Email.tsx +11 -13
  25. package/lib/field/Group/Group.tsx +10 -8
  26. package/lib/field/Integer/Integer.tsx +5 -7
  27. package/lib/field/Location/Location.css +4 -0
  28. package/lib/field/Location/Location.tsx +258 -0
  29. package/lib/field/Location/config-ext.json +8 -0
  30. package/lib/field/Location/index.tsx +1 -0
  31. package/lib/field/Multiselect/utils.ts +1 -1
  32. package/lib/field/ObjectReference/ObjectReference.tsx +235 -0
  33. package/lib/field/ObjectReference/index.tsx +1 -0
  34. package/lib/field/ObjectReference/utils.ts +111 -0
  35. package/lib/field/Percentage/Percentage.tsx +9 -9
  36. package/lib/field/Phone/Phone.tsx +7 -5
  37. package/lib/field/RadioButtons/RadioButtons.tsx +47 -5
  38. package/lib/field/RichText/RichText.css +79 -0
  39. package/lib/field/RichText/RichText.tsx +3 -1
  40. package/lib/field/ScalarList/ScalarList.tsx +2 -4
  41. package/lib/field/SelectableCard/SelectableCard.tsx +175 -0
  42. package/lib/field/SelectableCard/index.tsx +1 -0
  43. package/lib/field/SelectableCard/utils.tsx +223 -0
  44. package/lib/field/SemanticLink/SemanticLink.tsx +160 -28
  45. package/lib/field/SemanticLink/utils.ts +1 -1
  46. package/lib/field/TextArea/TextArea.tsx +5 -7
  47. package/lib/field/TextContent/TextContent.tsx +1 -2
  48. package/lib/field/TextInput/TextInput.tsx +5 -7
  49. package/lib/field/Time/Time.tsx +4 -8
  50. package/lib/field/URL/URL.tsx +5 -7
  51. package/lib/field/UserReference/UserReference.tsx +3 -6
  52. package/lib/helpers/attachmentShared.ts +6 -0
  53. package/lib/helpers/common-utils.ts +24 -2
  54. package/lib/helpers/data_page.ts +0 -1
  55. package/lib/helpers/field-group-utils.ts +1 -1
  56. package/lib/helpers/formatters/Currency.ts +20 -20
  57. package/lib/helpers/formatters/CurrencyMap.ts +0 -2
  58. package/lib/helpers/formatters/common.ts +2 -1
  59. package/lib/helpers/formatters/index.ts +2 -4
  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 +39 -36
  66. package/lib/infra/AssignmentCard/AssignmentCard.tsx +2 -2
  67. package/lib/infra/Containers/FlowContainer/FlowContainer.tsx +21 -113
  68. package/lib/infra/Containers/FlowContainer/helpers.ts +1 -5
  69. package/lib/infra/Containers/ModalViewContainer/ListViewActionButtons/ListViewActionButtons.tsx +4 -3
  70. package/lib/infra/Containers/ModalViewContainer/ModalViewContainer.tsx +7 -6
  71. package/lib/infra/Containers/SimpleView/helper.ts +1 -1
  72. package/lib/infra/Containers/ViewContainer/ViewContainer.tsx +4 -5
  73. package/lib/infra/Containers/container-helpers.ts +52 -0
  74. package/lib/infra/DashboardFilter/DashboardFilter.tsx +5 -9
  75. package/lib/infra/DashboardFilter/filterUtils.tsx +3 -6
  76. package/lib/infra/DeferLoad/DeferLoad.tsx +7 -9
  77. package/lib/infra/ErrorBoundary/ErrorBoundary.tsx +1 -3
  78. package/lib/infra/MultiStep/MultiStep.css +48 -70
  79. package/lib/infra/MultiStep/MultiStep.tsx +27 -58
  80. package/lib/infra/NavBar/NavBar.css +1 -1
  81. package/lib/infra/NavBar/NavBar.tsx +43 -32
  82. package/lib/infra/Reference/Reference.tsx +3 -4
  83. package/lib/infra/Region/Region.tsx +1 -1
  84. package/lib/infra/RootContainer/RootContainer.tsx +3 -4
  85. package/lib/infra/Stages/Stages.tsx +3 -4
  86. package/lib/infra/View/View.tsx +4 -3
  87. package/lib/template/AdvancedSearch/AdvancedSearch.tsx +86 -0
  88. package/lib/template/AdvancedSearch/SearchGroup/persistUtils.ts +52 -0
  89. package/lib/template/AdvancedSearch/SearchGroups/SearchGroups.tsx +244 -0
  90. package/lib/template/AdvancedSearch/SearchGroups/hooks.ts +37 -0
  91. package/lib/template/AdvancedSearch/SearchGroups/index.tsx +1 -0
  92. package/lib/template/AdvancedSearch/SearchGroups/utils.ts +29 -0
  93. package/lib/template/AdvancedSearch/TemplateContext.ts +11 -0
  94. package/lib/template/AdvancedSearch/config-ext.json +9 -0
  95. package/lib/template/AdvancedSearch/index.tsx +1 -0
  96. package/lib/template/AppShell/AppShell.css +1 -1
  97. package/lib/template/AppShell/AppShell.tsx +22 -23
  98. package/lib/template/BannerPage/BannerPage.tsx +2 -2
  99. package/lib/template/CaseSummary/CaseSummary.tsx +28 -41
  100. package/lib/template/CaseView/CaseView.tsx +32 -38
  101. package/lib/template/CaseViewActionsMenu/CaseViewActionsMenu.tsx +1 -1
  102. package/lib/template/Confirmation/Confirmation.tsx +3 -4
  103. package/lib/template/DataReference/DataReference.tsx +312 -106
  104. package/lib/template/DataReference/DataReferenceAdvancedSearchContext.ts +10 -0
  105. package/lib/template/DataReference/SearchForm.tsx +149 -0
  106. package/lib/template/DataReference/utils.ts +90 -0
  107. package/lib/template/DefaultForm/DefaultForm.tsx +3 -3
  108. package/lib/template/DefaultForm/utils/index.ts +1 -3
  109. package/lib/template/DefaultPage/DefaultPage.tsx +108 -0
  110. package/lib/template/DefaultPage/index.tsx +1 -0
  111. package/lib/template/Details/Details/Details.tsx +11 -11
  112. package/lib/template/Details/DetailsSubTabs/DetailsSubTabs.tsx +2 -2
  113. package/lib/template/Details/DetailsThreeColumn/DetailsThreeColumn.tsx +11 -11
  114. package/lib/template/Details/DetailsTwoColumn/DetailsTwoColumn.tsx +11 -11
  115. package/lib/template/Details/DynamicTabs/DynamicTabs.tsx +1 -2
  116. package/lib/template/FieldGroupTemplate/FieldGroupTemplate.tsx +2 -5
  117. package/lib/template/InlineDashboard/InlineDashboard.tsx +14 -16
  118. package/lib/template/InlineDashboardPage/InlineDashboardPage.tsx +2 -2
  119. package/lib/template/ListPage/ListPage.tsx +1 -1
  120. package/lib/template/ListView/ListView.tsx +285 -204
  121. package/lib/template/ListView/hooks.ts +1 -5
  122. package/lib/template/ListView/utils.ts +38 -6
  123. package/lib/template/MultiReferenceReadOnly/MultiReferenceReadOnly.tsx +1 -1
  124. package/lib/template/NarrowWide/NarrowWide/NarrowWide.tsx +5 -5
  125. package/lib/template/NarrowWide/NarrowWideDetails/NarrowWideDetails.tsx +11 -11
  126. package/lib/template/NarrowWide/NarrowWideForm/NarrowWideForm.tsx +2 -2
  127. package/lib/template/NarrowWide/NarrowWidePage/NarrowWidePage.tsx +2 -2
  128. package/lib/template/OneColumn/OneColumn/OneColumn.tsx +7 -7
  129. package/lib/template/OneColumn/OneColumnPage/OneColumnPage.tsx +1 -1
  130. package/lib/template/OneColumn/OneColumnTab/OneColumnTab.tsx +2 -2
  131. package/lib/template/PromotedFilters/PromotedFilters.tsx +1 -3
  132. package/lib/template/SelfServiceCaseView/SelfServiceCaseView.tsx +145 -0
  133. package/lib/template/SelfServiceCaseView/index.tsx +1 -0
  134. package/lib/template/SimpleTable/SimpleTable/SimpleTable.tsx +2 -5
  135. package/lib/template/SimpleTable/SimpleTableManual/SimpleTableManual.tsx +99 -84
  136. package/lib/template/SimpleTable/SimpleTableSelect/SimpleTableSelect.tsx +3 -5
  137. package/lib/template/SingleReferenceReadOnly/SingleReferenceReadOnly.tsx +10 -2
  138. package/lib/template/SubTabs/SubTabs.tsx +2 -2
  139. package/lib/template/SubTabs/tabUtils.ts +118 -1
  140. package/lib/template/TwoColumn/TwoColumn/TwoColumn.tsx +9 -10
  141. package/lib/template/TwoColumn/TwoColumnPage/TwoColumnPage.tsx +1 -1
  142. package/lib/template/TwoColumn/TwoColumnTab/TwoColumnTab.tsx +9 -10
  143. package/lib/template/WideNarrow/WideNarrow/WideNarrow.tsx +5 -5
  144. package/lib/template/WideNarrow/WideNarrowDetails/WideNarrowDetails.tsx +11 -11
  145. package/lib/template/WideNarrow/WideNarrowForm/WideNarrowForm.tsx +2 -2
  146. package/lib/template/WideNarrow/WideNarrowPage/WideNarrowPage.tsx +2 -2
  147. package/lib/template/WssNavBar/WssNavBar.css +1 -1
  148. package/lib/template/WssNavBar/WssNavBar.tsx +6 -6
  149. package/lib/template/utils.tsx +58 -0
  150. package/lib/widget/AppAnnouncement/AppAnnouncement.tsx +1 -1
  151. package/lib/widget/Attachment/Attachment.css +7 -8
  152. package/lib/widget/Attachment/Attachment.tsx +304 -231
  153. package/lib/widget/Attachment/Attachment.types.ts +96 -0
  154. package/lib/widget/Attachment/AttachmentUtils.ts +316 -0
  155. package/lib/widget/CaseHistory/CaseHistory.tsx +5 -5
  156. package/lib/widget/FileUtility/ActionButtonsForFileUtil/ActionButtonsForFileUtil.css +0 -14
  157. package/lib/widget/FileUtility/ActionButtonsForFileUtil/ActionButtonsForFileUtil.tsx +3 -3
  158. package/lib/widget/FileUtility/FileUtility/FileUtility.css +7 -6
  159. package/lib/widget/FileUtility/FileUtility/FileUtility.tsx +30 -23
  160. package/lib/widget/Followers/Followers.tsx +2 -4
  161. package/lib/widget/QuickCreate/QuickCreate.tsx +1 -3
  162. package/lib/widget/SummaryItem/SummaryItem.css +9 -11
  163. package/lib/widget/SummaryItem/SummaryItem.tsx +3 -3
  164. package/lib/widget/SummaryList/SummaryList.tsx +1 -1
  165. package/lib/widget/ToDo/ToDo.css +1 -13
  166. package/lib/widget/ToDo/ToDo.tsx +38 -37
  167. package/package.json +1 -1
  168. package/lib/helpers/attachmentHelpers.ts +0 -76
  169. package/lib/infra/Containers/helpers.ts +0 -6
@@ -1,175 +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 } = 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
- // @ts-ignore
159
- messages: [
160
- {
161
- type: 'error',
162
- // @ts-ignore - Type '{ type: string; message: string; }' is not assignable to type 'MessagesConfigObject'.
163
- message: pConn.getLocalizedValue('Error with one or more files', '', '')
164
- }
165
- ],
166
- property: fieldName,
167
- pageReference: pConn.getPageReference(),
168
- context
169
- });
170
- 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);
171
188
  }
172
- return f;
189
+ return localFile;
173
190
  });
174
191
  });
175
192
  }
@@ -177,81 +194,62 @@ export default function Attachment(props: AttachmentProps) {
177
194
  };
178
195
  };
179
196
 
180
- const validateFileExtension = (fileObj, allowedExtensions) => {
181
- if (!allowedExtensions) {
182
- return true;
183
- }
184
- const allowedExtensionList = allowedExtensions
185
- .toLowerCase()
186
- .split(',')
187
- .map(item => item.replaceAll('.', '').trim());
188
- const extension = fileObj.name.split('.').pop().toLowerCase();
189
- return allowedExtensionList.includes(extension);
190
- };
191
-
192
- const clearFieldErrorMessages = () => {
193
- const fieldName = (pConn.getStateProps() as any).value;
194
- const context = pConn.getContextName();
195
- // @ts-ignore
196
- PCore.getMessageManager().clearMessages({
197
- type: PCore.getConstants().MESSAGES.MESSAGES_TYPE_ERROR,
198
- property: fieldName,
199
- pageReference: pConn.getPageReference(),
200
- context
201
- });
202
- };
203
-
204
197
  const onFileAdded = event => {
205
198
  let addedFiles = Array.from(event.target.files);
206
- addedFiles = allowMultiple === 'true' ? addedFiles : [addedFiles[0]];
199
+ addedFiles = allowMultiple ? addedFiles : [addedFiles[0]];
207
200
  const maxAttachmentSize = PCore.getEnvironmentInfo().getMaxAttachmentSize() || '5';
208
201
  const tempFilesToBeUploaded = [
209
202
  ...addedFiles.map((f: any, index) => {
210
203
  f.ID = `${new Date().getTime()}I${index}`;
211
- f.inProgress = true;
212
204
  f.props = {
213
205
  type: f.type,
214
206
  name: f.name,
207
+ id: f.ID,
208
+ format: f.name.split('.').pop(),
215
209
  icon: getIconFromFileType(f.type),
216
- onDelete: () => deleteFile(f)
210
+ onDelete: () => deleteFile(f, index),
211
+ thumbnail: window.URL.createObjectURL(f)
217
212
  };
218
213
  if (!validateMaxSize(f, maxAttachmentSize)) {
219
214
  f.props.error = true;
220
- f.inProgress = false;
221
- 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.`);
222
216
  } else if (!validateFileExtension(f, extensions)) {
223
217
  f.props.error = true;
224
- f.inProgress = false;
225
- 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(
226
219
  '.',
227
220
  ''
228
221
  )}`;
229
222
  }
230
223
  if (f.props.error) {
231
224
  const fieldName = (pConn.getStateProps() as any).value;
232
- const context = pConn.getContextName();
233
225
  PCore.getMessageManager().addMessages({
234
- // @ts-ignore
235
226
  messages: [
236
227
  {
237
228
  type: 'error',
238
- // @ts-ignore - Type '{ type: string; message: string; }' is not assignable to type 'MessagesConfigObject'.
239
- message: pConn.getLocalizedValue('Error with one or more files', '', '')
229
+ message: localizationService.getLocalizedText('Error with one or more files')
240
230
  }
241
231
  ],
242
232
  property: fieldName,
243
233
  pageReference: pConn.getPageReference(),
244
- context
234
+ context: contextName
245
235
  });
246
236
  }
247
237
  return f;
248
238
  })
249
239
  ];
240
+
250
241
  const tempFilesWithError = tempFilesToBeUploaded.filter(f => f.props.error);
251
242
  if (tempFilesWithError.length > 0) {
252
- 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);
253
251
  }
254
- setFiles(current => (allowMultiple !== 'true' ? [...tempFilesToBeUploaded] : [...current, ...tempFilesToBeUploaded]));
252
+ setFiles(current => (!allowMultiple ? [...tempFilesToBeUploaded] : [...current, ...tempFilesToBeUploaded]));
255
253
  setToggleUploadBegin(true);
256
254
  };
257
255
 
@@ -260,106 +258,178 @@ export default function Attachment(props: AttachmentProps) {
260
258
  .filter(e => {
261
259
  const isFileUploaded = e.props && e.props.progress === 100;
262
260
  const fileHasError = e.props && e.props.error;
263
- const isFileUploadedinLastStep = e.responseProps && e.responseProps.pzInsKey;
264
- return !isFileUploaded && !fileHasError && !isFileUploadedinLastStep;
261
+ const isFileUploadedInLastStep = e.responseProps && e.responseProps.ID !== 'temp';
262
+ const isFileUploadInProgress = e.inProgress;
263
+ return !isFileUploadInProgress && !isFileUploaded && !fileHasError && !isFileUploadedInLastStep;
265
264
  })
266
- .map(f =>
267
- window.PCore.getAttachmentUtils().uploadAttachment(
268
- f,
269
- () => {
270
- onUploadProgress();
265
+ .map(file =>
266
+ PCore.getAttachmentUtils().uploadAttachment(
267
+ file,
268
+ ev => {
269
+ onUploadProgress(file.props.id, ev);
271
270
  },
272
271
  isFetchCanceled => {
273
- return errorHandler(isFetchCanceled, f);
272
+ return errorHandler(isFetchCanceled, file);
274
273
  },
275
- pConn.getContextName()
274
+ contextName
276
275
  )
277
276
  );
277
+
278
+ // allow new files to be added when other files upload is still in progress
279
+ setToggleUploadBegin(false);
278
280
  Promise.allSettled(filesToBeUploaded)
279
281
  .then((fileResponses: any) => {
280
282
  fileResponses = fileResponses.filter(fr => fr.status !== 'rejected'); // in case of deleting an in progress file, promise gets cancelled but still enters then block
281
283
  if (fileResponses.length > 0) {
282
- setFiles(current => {
283
- const tempFilesUploaded = [...current];
284
- tempFilesUploaded.forEach(f => {
285
- 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);
286
291
  if (index >= 0) {
287
- f.props.meta = pConn.getLocalizedValue('Uploaded successfully', '', '');
288
- f.props.progress = 100;
289
- f.inProgress = false;
290
- f.handle = fileResponses[index].value.ID;
291
- f.label = valueRef;
292
- f.category = categoryName;
293
- f.responseProps = {
294
- pzInsKey: 'temp',
295
- 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'
296
301
  };
297
302
  }
298
303
  });
299
304
  return tempFilesUploaded;
300
305
  });
301
306
 
302
- if (filesWithError.length === 0) {
303
- 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);
304
318
  }
305
319
  }
306
- setToggleUploadBegin(false);
320
+ actionSequencer.deRegisterBlockingAction(contextName).catch(() => {});
307
321
  })
308
322
  .catch(error => {
309
- // eslint-disable-next-line no-console
310
323
  console.log(error);
311
324
  setToggleUploadBegin(false);
312
325
  });
313
- }, [files, filesWithError]);
326
+ }, [files]);
314
327
 
315
328
  useEffect(() => {
316
329
  if (toggleUploadBegin && files.length > 0) {
317
- uploadFiles();
330
+ actionSequencer.registerBlockingAction(contextName).then(() => {
331
+ uploadFiles();
332
+ });
318
333
  }
319
334
  }, [toggleUploadBegin]);
320
335
 
321
336
  useEffect(() => {
322
- if (files.length > 0 && displayMode !== 'DISPLAY_ONLY') {
323
- const currentAttachmentList = getCurrentAttachmentsList(getAttachmentKey(valueRef), pConn.getContextName());
324
- // block duplicate files to redux store when added 1 after another to prevent multiple duplicates being added to the case on submit
325
- const tempFiles = files.filter(f => currentAttachmentList.findIndex(fr => fr.ID === f.ID) === -1 && !f.inProgress && f.responseProps);
326
-
327
- const updatedAttList = [...currentAttachmentList, ...tempFiles];
328
- updateAttachmentState(pConn, getAttachmentKey(valueRef), updatedAttList);
337
+ if (filesWithError.current.length === 0) {
338
+ clearFieldErrorMessages(pConn);
329
339
  }
330
- }, [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
+ };
331
366
 
332
367
  useEffect(() => {
333
- if (filesWithError.length === 0) {
334
- 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
+ );
335
377
  }
336
- }, [filesWithError]);
337
378
 
338
- useEffect(() => {
339
- let tempUploadedFiles = getCurrentAttachmentsList(getAttachmentKey(valueRef), pConn.getContextName());
340
- tempUploadedFiles = tempUploadedFiles.filter(f => f.label === valueRef);
341
- setFiles(current => {
342
- return [
343
- ...current.map(f => {
344
- return f.responseProps.pzInsKey && !f.responseProps.pzInsKey.includes('temp')
345
- ? {
346
- ...f,
347
- props: {
348
- ...f.props,
349
- onDelete: () => deleteFile(f)
350
- }
351
- }
352
- : { ...f };
353
- }),
354
- ...tempUploadedFiles
355
- ];
356
- });
357
- 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
+
358
384
  return () => {
359
- 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
+ }
360
388
  };
361
389
  }, []);
362
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
+
363
433
  const handleClick = event => {
364
434
  setAnchorEl(event.currentTarget);
365
435
  };
@@ -380,13 +450,13 @@ export default function Attachment(props: AttachmentProps) {
380
450
  id={valueRef}
381
451
  name='upload-photo'
382
452
  type='file'
383
- multiple={allowMultiple === 'true'}
453
+ multiple={allowMultiple}
384
454
  required={required}
385
455
  disabled={disabled}
386
456
  onChange={onFileAdded}
387
457
  />
388
458
  <Button style={{ textTransform: 'none' }} variant='outlined' color='primary' component='span'>
389
- {allowMultiple === 'true'
459
+ {allowMultiple
390
460
  ? uploadMultipleFilesLabel === 'file_upload_text_multiple'
391
461
  ? 'Choose files'
392
462
  : uploadMultipleFilesLabel
@@ -420,7 +490,7 @@ export default function Attachment(props: AttachmentProps) {
420
490
  </div>
421
491
  <div className='psdk-utility-action'>
422
492
  {item.ID && (
423
- <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)}>
424
494
  <img className='psdk-utility-card-action-svg-icon' src={deleteIcon} />
425
495
  </button>
426
496
  )}
@@ -440,11 +510,14 @@ export default function Attachment(props: AttachmentProps) {
440
510
  <MenuItem
441
511
  style={{ fontSize: '14px' }}
442
512
  key='download'
443
- onClick={() => downloadFile(item.responseProps ? item.responseProps : {})}
513
+ onClick={() => {
514
+ setAnchorEl(null);
515
+ onFileDownload(item.responseProps ? item.responseProps : {});
516
+ }}
444
517
  >
445
518
  Download
446
519
  </MenuItem>
447
- <MenuItem style={{ fontSize: '14px' }} key='delete' onClick={() => deleteFile(item)}>
520
+ <MenuItem style={{ fontSize: '14px' }} key='delete' onClick={() => deleteFile(item, index)}>
448
521
  Delete
449
522
  </MenuItem>
450
523
  </Menu>
@@ -460,8 +533,8 @@ export default function Attachment(props: AttachmentProps) {
460
533
  return (
461
534
  <div className='file-upload-container'>
462
535
  <span className={`label ${required ? 'file-label' : ''}`}>{label}</span>
463
- {((files.length === 0 && allowMultiple !== 'true') || allowMultiple === 'true') && <section>{content}</section>}
464
- {validatemessage !== '' ? <span className='file-error'>{validatemessage}</span> : ''}
536
+ {((files.length === 0 && !allowMultiple) || allowMultiple) && <section>{content}</section>}
537
+ {validatemessage !== '' ? <span className='file-error'>{validatemessage}</span> : <span style={{ fontSize: '14px' }}>{helperText}</span>}
465
538
  {files && files.length > 0 && <section>{fileDisplay}</section>}
466
539
  </div>
467
540
  );