Skip to content

Commit 5f5017f

Browse files
authored
fix: attachments not getting displayed on the field level (#471)
1 parent 17d280a commit 5f5017f

File tree

2 files changed

+117
-87
lines changed

2 files changed

+117
-87
lines changed
Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,67 @@
1+
import { useCallback } from 'react';
12
import download from 'downloadjs';
23

3-
export const validateMaxSize = (fileObj: any, maxSizeInMB: string): boolean => {
4-
const fileSize = (fileObj.size / 1048576).toFixed(2);
5-
return parseFloat(fileSize) < parseFloat(maxSizeInMB);
4+
export const isContentBinary = headers => {
5+
return headers && headers['content-transfer-encoding'] === 'binary';
6+
};
7+
8+
export const isContentBase64 = headers => {
9+
return headers && headers['content-transfer-encoding'] === 'base64';
610
};
711

8-
export const fileDownload = (data, fileName, ext) => {
12+
export const fileDownload = (data, fileName, ext, headers) => {
913
const name = ext ? `${fileName}.${ext}` : fileName;
1014
// Temp fix: downloading EMAIl type attachment as html file
1115
if (ext === 'html') {
12-
download(data, name, 'text/html');
16+
download(isContentBase64(headers) ? atob(data) : data, name, 'text/html');
17+
} else if (isContentBinary(headers)) {
18+
download(data, name);
1319
} else {
1420
download(atob(data), name);
1521
}
1622
};
1723

24+
export const fileDownloadVar = (content, type, name, extension) => {
25+
if (type === 'FILE' || type === undefined) {
26+
fileDownload(content.data, name, extension, content.headers);
27+
} else if (type === 'URL') {
28+
let { data } = content;
29+
if (!/^(http|https):\/\//.test(data)) {
30+
data = `//${data}`;
31+
}
32+
window.open(content.data, '_blank');
33+
} else if (type === 'EMAIL') {
34+
// Temp Fix: for EMAIL type attachment
35+
fileDownload(content.data, name, 'html', content.headers);
36+
}
37+
};
38+
39+
export const useFileDownload = context => {
40+
return useCallback(
41+
({ ID, name, extension, type, category, responseType }) => {
42+
if (category !== 'pxDocument') {
43+
PCore.getAttachmentUtils()
44+
.downloadAttachment(ID, context, responseType)
45+
.then(content => {
46+
fileDownloadVar(content, type, name, extension);
47+
})
48+
// eslint-disable-next-line no-console
49+
.catch(console.error);
50+
} else {
51+
PCore.getAttachmentUtils()
52+
// @ts-ignore
53+
.downloadDocument(ID, context, responseType)
54+
.then(content => {
55+
fileDownloadVar(content, type, name, extension);
56+
})
57+
// eslint-disable-next-line no-console
58+
.catch(console.error);
59+
}
60+
},
61+
[context]
62+
);
63+
};
64+
1865
export const getIconFromFileType = (fileType): string => {
1966
let icon = 'document-doc';
2067
if (!fileType) return icon;
@@ -42,35 +89,9 @@ export const getIconFromFileType = (fileType): string => {
4289
return icon;
4390
};
4491

45-
export const getIconForAttachment = (inThis: any, attachment: any): string => {
46-
let icon;
47-
switch (attachment.type) {
48-
case 'FILE':
49-
icon = inThis.getIconFromFileType(attachment.mimeType);
50-
break;
51-
case 'URL':
52-
icon = 'chain';
53-
break;
54-
default:
55-
icon = 'document-doc';
56-
}
57-
return icon;
92+
export const validateMaxSize = (fileObj: any, maxSizeInMB: string): boolean => {
93+
const fileSize = (fileObj.size / 1048576).toFixed(2);
94+
return parseFloat(fileSize) < parseFloat(maxSizeInMB);
5895
};
5996

60-
export const buildFilePropsFromResponse = (
61-
respObj
62-
): {
63-
props: { meta: string; name: string; icon: string };
64-
responseProps: any;
65-
} => {
66-
return {
67-
props: {
68-
meta: `${respObj.pyCategoryName}, ${respObj.pxCreateOperator}`,
69-
name: respObj.pyAttachName,
70-
icon: getIconFromFileType(respObj.pyMimeFileExtension)
71-
},
72-
responseProps: {
73-
...respObj
74-
}
75-
};
76-
};
97+
export const isFileUploadedToServer = file => file.responseProps && !file.responseProps.ID?.includes('temp');

packages/react-sdk-components/src/components/widget/Attachment/Attachment.tsx

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* eslint-disable react/jsx-boolean-value */
22
/* eslint-disable react/no-array-index-key */
33
/* eslint-disable no-nested-ternary */
4-
import { useState, useEffect, useCallback } from 'react';
4+
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
55
import { CircularProgress, IconButton, Menu, MenuItem, Button } from '@mui/material';
66
import MoreVertIcon from '@mui/icons-material/MoreVert';
7-
import download from 'downloadjs';
87

9-
import { buildFilePropsFromResponse, getIconFromFileType, validateMaxSize } from '../../helpers/attachmentHelpers';
8+
import { getIconFromFileType, isFileUploadedToServer, useFileDownload, validateMaxSize } from '../../helpers/attachmentHelpers';
9+
1010
import { Utils } from '../../helpers/utils';
1111
import { PConnFieldProps } from '../../../types/PConnProps';
1212

@@ -19,7 +19,9 @@ interface AttachmentProps extends Omit<PConnFieldProps, 'value'> {
1919
extensions: string;
2020
}
2121

22-
const getAttachmentKey = (name = '') => (name ? `attachmentsList.${name}` : 'attachmentsList');
22+
const getAttachmentKey = (name, embeddedReference) => {
23+
return `attachmentsList${embeddedReference}.${name}`;
24+
};
2325

2426
const getCurrentAttachmentsList = (key, context) => {
2527
return PCore.getStoreValue(`.${key}`, 'context_data', context) || [];
@@ -38,61 +40,65 @@ export default function Attachment(props: AttachmentProps) {
3840
let { required, disabled } = props;
3941
[required, disabled] = [required, disabled].map(prop => prop === true || (typeof prop === 'string' && prop === 'true'));
4042
const pConn = getPConnect();
43+
44+
const actionSequencer = useMemo(() => PCore.getActionsSequencer(), []);
4145
const caseID = PCore.getStoreValue('.pyID', 'caseInfo.content', pConn.getContextName());
4246
const localizedVal = PCore.getLocaleUtils().getLocaleValue;
4347
const localeCategory = 'CosmosFields';
4448
const uploadMultipleFilesLabel = localizedVal('file_upload_text_multiple', localeCategory);
4549
const uploadSingleFileLabel = localizedVal('file_upload_text_one', localeCategory);
46-
let categoryName = '';
47-
if (value && value.pyCategoryName) {
48-
categoryName = value.pyCategoryName;
49-
}
5050
const deleteIcon = Utils.getImageSrc('trash', Utils.getSDKStaticConentUrl());
5151
const srcImg = Utils.getImageSrc('document-doc', Utils.getSDKStaticConentUrl());
5252
let valueRef = (pConn.getStateProps() as any).value;
5353
valueRef = valueRef.indexOf('.') === 0 ? valueRef.substring(1) : valueRef;
5454
const [anchorEl, setAnchorEl] = useState(null);
5555
const open = Boolean(anchorEl);
56-
const [files, setFiles] = useState<any[]>(() =>
57-
value?.pxResults && +value.pyCount > 0 ? value.pxResults.map(f => buildFilePropsFromResponse(f)) : []
58-
);
56+
57+
const rawValue = pConn.getComponentConfig().value;
58+
const isAttachmentAnnotationPresent = typeof rawValue === 'object' ? false : rawValue?.includes('@ATTACHMENT');
59+
const { hasUploadedFiles, attachments, categoryName } = isAttachmentAnnotationPresent
60+
? value
61+
: PCore.getAttachmentUtils().prepareAttachmentData(value);
62+
63+
const fileInputRef = useRef<HTMLInputElement>(null);
64+
const [files, setFiles] = useState<any[]>(attachments);
5965
const [filesWithError, setFilesWithError] = useState<any[]>([]);
6066
const [toggleUploadBegin, setToggleUploadBegin] = useState(false);
6167

68+
const context = pConn.getContextName();
69+
const onFileDownload = useFileDownload(context);
70+
71+
let embeddedProperty = pConn
72+
.getPageReference()
73+
.replace(PCore.getConstants().CASE_INFO.CASE_INFO_CONTENT, '')
74+
.replace(PCore.getConstants().DATA_INFO.DATA_INFO_CONTENT, '');
75+
76+
if (valueRef?.indexOf('.') > 0) {
77+
embeddedProperty = valueRef.substring(0, valueRef.indexOf('.') + 1);
78+
}
79+
6280
const resetAttachmentStoredState = () => {
63-
PCore.getStateUtils().updateState(pConn.getContextName(), getAttachmentKey(valueRef), undefined, {
81+
PCore.getStateUtils().updateState(pConn.getContextName(), getAttachmentKey(valueRef, embeddedProperty), undefined, {
6482
pageReference: 'context_data',
6583
isArrayDeepMerge: false
6684
});
6785
};
6886

69-
const fileDownload = (data, fileName, ext) => {
70-
const fileData = ext ? `${fileName}.${ext}` : fileName;
71-
download(atob(data), fileData);
72-
};
73-
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-
};
85-
8687
const deleteFile = useCallback(
8788
file => {
8889
setAnchorEl(null);
90+
91+
// reset the file input so that it will allow re-uploading the same file after deletion
92+
if (fileInputRef.current) {
93+
fileInputRef.current.value = ''; // Reset the input
94+
}
95+
8996
let attachmentsList: any[] = [];
90-
let currentAttachmentList = getCurrentAttachmentsList(getAttachmentKey(valueRef), pConn.getContextName());
97+
let currentAttachmentList = getCurrentAttachmentsList(getAttachmentKey(valueRef, embeddedProperty), pConn.getContextName());
9198

9299
// If file to be deleted is the one added in previous stage i.e. for which a file instance is created in server
93100
// 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') {
101+
if (hasUploadedFiles && isFileUploadedToServer(file)) {
96102
const updatedAttachments = files.map(f => {
97103
if (f.responseProps && f.responseProps.pzInsKey === file.responseProps.pzInsKey) {
98104
return { ...f, delete: true, label: valueRef };
@@ -101,27 +107,26 @@ export default function Attachment(props: AttachmentProps) {
101107
});
102108

103109
// updating the redux store to help form-handler in passing the data to delete the file from server
104-
updateAttachmentState(pConn, getAttachmentKey(valueRef), [...updatedAttachments]);
110+
updateAttachmentState(pConn, getAttachmentKey(valueRef, embeddedProperty), [...updatedAttachments]);
105111
setFiles(current => {
106112
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-
);
113+
const filesPostDelete = current.filter(f => isFileUploadedToServer(f) && f.responseProps?.ID !== file.responseProps?.ID);
110114
attachmentsList = [...filesPostDelete, ...newlyAddedFiles];
111115
return attachmentsList;
112116
});
113117
} // if the file being deleted is the added in this stage i.e. whose data is not yet created in server
114118
else {
115119
// 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;
120-
});
121-
updateAttachmentState(pConn, getAttachmentKey(valueRef), [...currentAttachmentList, ...attachmentsList]);
120+
currentAttachmentList = currentAttachmentList.filter(f => !f.props.error && (f.delete || f.label !== valueRef));
121+
setFiles(current => current.filter(f => f.ID !== file.ID));
122+
updateAttachmentState(pConn, getAttachmentKey(valueRef, embeddedProperty), [...currentAttachmentList, ...attachmentsList]);
122123
if (file.inProgress) {
123124
// @ts-ignore - 3rd parameter "responseEncoding" should be optional
124125
PCore.getAttachmentUtils().cancelRequest(file.ID, pConn.getContextName());
126+
actionSequencer.deRegisterBlockingAction(pConn.getContextName()).catch(error => {
127+
// eslint-disable-next-line no-console
128+
console.log(error);
129+
});
125130
}
126131
}
127132

@@ -130,7 +135,7 @@ export default function Attachment(props: AttachmentProps) {
130135
return prevFilesWithError.filter(f => f.ID !== file.ID);
131136
});
132137
},
133-
[pConn, value, valueRef, filesWithError]
138+
[valueRef, pConn, hasUploadedFiles, filesWithError, hasUploadedFiles, actionSequencer]
134139
);
135140

136141
const onUploadProgress = () => {};
@@ -152,7 +157,6 @@ export default function Attachment(props: AttachmentProps) {
152157
f.props.name = pConn.getLocalizedValue('Unable to upload file', '', '');
153158
f.inProgress = false;
154159
const fieldName = (pConn.getStateProps() as any).value;
155-
const context = pConn.getContextName();
156160
// set errors to property to block submit even on errors in file upload
157161
PCore.getMessageManager().addMessages({
158162
messages: [
@@ -189,7 +193,6 @@ export default function Attachment(props: AttachmentProps) {
189193

190194
const clearFieldErrorMessages = () => {
191195
const fieldName = (pConn.getStateProps() as any).value;
192-
const context = pConn.getContextName();
193196
PCore.getMessageManager().clearMessages({
194197
type: PCore.getConstants().MESSAGES.MESSAGES_TYPE_ERROR,
195198
property: fieldName,
@@ -226,7 +229,6 @@ export default function Attachment(props: AttachmentProps) {
226229
}
227230
if (f.props.error) {
228231
const fieldName = (pConn.getStateProps() as any).value;
229-
const context = pConn.getContextName();
230232
PCore.getMessageManager().addMessages({
231233
messages: [
232234
{
@@ -315,12 +317,12 @@ export default function Attachment(props: AttachmentProps) {
315317

316318
useEffect(() => {
317319
if (files.length > 0 && displayMode !== 'DISPLAY_ONLY') {
318-
const currentAttachmentList = getCurrentAttachmentsList(getAttachmentKey(valueRef), pConn.getContextName());
320+
const currentAttachmentList = getCurrentAttachmentsList(getAttachmentKey(valueRef, embeddedProperty), pConn.getContextName());
319321
// block duplicate files to redux store when added 1 after another to prevent multiple duplicates being added to the case on submit
320322
const tempFiles = files.filter(f => currentAttachmentList.findIndex(fr => fr.ID === f.ID) === -1 && !f.inProgress && f.responseProps);
321323

322324
const updatedAttList = [...currentAttachmentList, ...tempFiles];
323-
updateAttachmentState(pConn, getAttachmentKey(valueRef), updatedAttList);
325+
updateAttachmentState(pConn, getAttachmentKey(valueRef, embeddedProperty), updatedAttList);
324326
}
325327
}, [files]);
326328

@@ -331,7 +333,7 @@ export default function Attachment(props: AttachmentProps) {
331333
}, [filesWithError]);
332334

333335
useEffect(() => {
334-
let tempUploadedFiles = getCurrentAttachmentsList(getAttachmentKey(valueRef), pConn.getContextName());
336+
let tempUploadedFiles = getCurrentAttachmentsList(getAttachmentKey(valueRef, embeddedProperty), pConn.getContextName());
335337
tempUploadedFiles = tempUploadedFiles.filter(f => f.label === valueRef);
336338
setFiles(current => {
337339
return [
@@ -349,9 +351,13 @@ export default function Attachment(props: AttachmentProps) {
349351
...tempUploadedFiles
350352
];
351353
});
352-
PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION, resetAttachmentStoredState, caseID);
354+
if (displayMode !== 'DISPLAY_ONLY') {
355+
PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION, resetAttachmentStoredState, caseID);
356+
}
353357
return () => {
354-
PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION, caseID);
358+
if (displayMode !== 'DISPLAY_ONLY') {
359+
PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION, caseID);
360+
}
355361
};
356362
}, []);
357363

@@ -435,7 +441,10 @@ export default function Attachment(props: AttachmentProps) {
435441
<MenuItem
436442
style={{ fontSize: '14px' }}
437443
key='download'
438-
onClick={() => downloadFile(item.responseProps ? item.responseProps : {})}
444+
onClick={() => {
445+
setAnchorEl(null);
446+
onFileDownload(item.responseProps ? item.responseProps : {});
447+
}}
439448
>
440449
Download
441450
</MenuItem>

0 commit comments

Comments
 (0)