@evoke-platform/ui-components 1.12.0-dev.1 → 1.12.0-dev.3
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.
- package/dist/published/components/custom/Form/Common/Form.js +15 -7
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +6 -1
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +20 -6
- package/dist/published/components/custom/Form/utils.js +16 -12
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +18 -3
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +5 -9
- package/dist/published/components/custom/FormV2/components/types.d.ts +8 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +5 -1
- package/dist/published/components/custom/FormV2/components/utils.js +49 -12
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +2 -2
- package/package.json +1 -1
|
@@ -62,6 +62,7 @@ export function Form(props) {
|
|
|
62
62
|
TextField: FormFieldComponent,
|
|
63
63
|
Decimal: FormFieldComponent,
|
|
64
64
|
Document: DocumentComponent,
|
|
65
|
+
File: DocumentComponent,
|
|
65
66
|
Integer: FormFieldComponent,
|
|
66
67
|
Buttons: ButtonComponent,
|
|
67
68
|
Content: Components.components.content,
|
|
@@ -241,7 +242,8 @@ export function Form(props) {
|
|
|
241
242
|
else if (object?.properties) {
|
|
242
243
|
// If form.io form is not configured and no inputProperties are
|
|
243
244
|
// set, use object properties to build form.
|
|
244
|
-
const propertiesInForm = object.properties.filter((prop) => prop.id !== associatedObject?.propertyId &&
|
|
245
|
+
const propertiesInForm = object.properties.filter((prop) => prop.id !== associatedObject?.propertyId &&
|
|
246
|
+
(action?.type !== 'create' || !['document', 'file'].includes(prop.type)));
|
|
245
247
|
const components = buildComponentPropsFromObjectProperties(propertiesInForm, object.id, instance, {
|
|
246
248
|
...objectInputCommonProps,
|
|
247
249
|
defaultPages: allDefaultPages,
|
|
@@ -284,8 +286,8 @@ export function Form(props) {
|
|
|
284
286
|
};
|
|
285
287
|
const saveDocuments = async (submittedFields, action) => {
|
|
286
288
|
const documentProperties = action?.parameters
|
|
287
|
-
? action.parameters.filter((param) => param.type
|
|
288
|
-
: object?.properties?.filter((prop) => prop.type
|
|
289
|
+
? action.parameters.filter((param) => ['document', 'file'].includes(param.type))
|
|
290
|
+
: object?.properties?.filter((prop) => ['document', 'file'].includes(prop.type));
|
|
289
291
|
let allEntries = null;
|
|
290
292
|
for (const docProperty of documentProperties ?? []) {
|
|
291
293
|
const currentValue = submittedFields[docProperty.id];
|
|
@@ -330,8 +332,8 @@ export function Form(props) {
|
|
|
330
332
|
};
|
|
331
333
|
const deleteDocuments = async (submittedFields, requestSuccess, action) => {
|
|
332
334
|
const documentProperties = action?.parameters
|
|
333
|
-
? action.parameters.filter((param) => param.type
|
|
334
|
-
: object?.properties?.filter((prop) => prop.type
|
|
335
|
+
? action.parameters.filter((param) => ['document', 'file'].includes(param.type))
|
|
336
|
+
: object?.properties?.filter((prop) => ['document', 'file'].includes(prop.type));
|
|
335
337
|
for (const docProperty of documentProperties ?? []) {
|
|
336
338
|
const savedValue = submittedFields[docProperty.id];
|
|
337
339
|
const originalValue = instance?.[docProperty.id];
|
|
@@ -340,11 +342,17 @@ export function Form(props) {
|
|
|
340
342
|
: (savedValue?.filter((file) => !originalValue?.some((f) => f.id === file.id)) ?? []);
|
|
341
343
|
for (const doc of documentsToRemove) {
|
|
342
344
|
try {
|
|
343
|
-
|
|
345
|
+
// Use different endpoints based on property type
|
|
346
|
+
const deleteEndpoint = docProperty.type === 'file'
|
|
347
|
+
? getPrefixedUrl(`/files/${doc.id}`)
|
|
348
|
+
: getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/${doc.id}`);
|
|
349
|
+
await apiServices?.delete(deleteEndpoint);
|
|
344
350
|
}
|
|
345
351
|
catch (error) {
|
|
346
352
|
if (error.response?.status !== 404) {
|
|
347
|
-
setSnackbarError({
|
|
353
|
+
setSnackbarError({
|
|
354
|
+
message: `An error occurred while removing ${docProperty.type === 'file' ? 'file' : 'document'} '${doc.name}'`,
|
|
355
|
+
});
|
|
348
356
|
}
|
|
349
357
|
}
|
|
350
358
|
}
|
|
@@ -40,8 +40,13 @@ export const Document = (props) => {
|
|
|
40
40
|
}, []);
|
|
41
41
|
const checkPermissions = () => {
|
|
42
42
|
if (canUpdateProperty) {
|
|
43
|
+
// For 'file' type properties, check permissions on the sys__file object
|
|
44
|
+
// For 'document' type properties, check document attachment permissions
|
|
45
|
+
const endpoint = property.type === 'file'
|
|
46
|
+
? getPrefixedUrl(`/objects/sys__file/instances/${instance.id}/checkAccess?action=update`)
|
|
47
|
+
: getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=update`);
|
|
43
48
|
apiServices
|
|
44
|
-
.get(
|
|
49
|
+
.get(endpoint)
|
|
45
50
|
.then((accessCheck) => setHasUpdatePermission(accessCheck.result))
|
|
46
51
|
.catch(() => setHasUpdatePermission(false));
|
|
47
52
|
}
|
package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js
CHANGED
|
@@ -36,13 +36,19 @@ export const DocumentList = (props) => {
|
|
|
36
36
|
}
|
|
37
37
|
}, [property]);
|
|
38
38
|
const getDocuments = (currentDocumentIds, shouldRetry = true) => {
|
|
39
|
-
|
|
39
|
+
// For 'file' type properties, fetch sys__file instances directly
|
|
40
|
+
// For 'document' type properties, fetch attachment documents
|
|
41
|
+
const endpoint = property.type === 'file'
|
|
42
|
+
? getPrefixedUrl(`/objects/sys__file/instances`)
|
|
43
|
+
: getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents`);
|
|
44
|
+
apiServices.get(endpoint, {
|
|
40
45
|
params: { filter: { where: { id: { inq: currentDocumentIds } } } },
|
|
41
46
|
}, (error, docs) => {
|
|
42
47
|
// There is a short delay between when a document is uploaded and when
|
|
43
48
|
// it is indexed. Therefore, try again if documents are not found.
|
|
44
49
|
if (shouldRetry &&
|
|
45
|
-
(!docs ||
|
|
50
|
+
(!docs ||
|
|
51
|
+
currentDocumentIds.some((docId) => !docs.find((doc) => docId === doc.id)))) {
|
|
46
52
|
setTimeout(() => getDocuments(currentDocumentIds, false), 2000);
|
|
47
53
|
}
|
|
48
54
|
else if (error) {
|
|
@@ -58,9 +64,12 @@ export const DocumentList = (props) => {
|
|
|
58
64
|
}, []);
|
|
59
65
|
const checkPermissions = () => {
|
|
60
66
|
if (instance?.[property.id]?.length) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
// For 'file' type properties, check permissions on the sys__file object
|
|
68
|
+
// For 'document' type properties, check document attachment permissions
|
|
69
|
+
const endpoint = property.type === 'file'
|
|
70
|
+
? getPrefixedUrl(`/objects/sys__file/instances/${instance.id}/checkAccess?action=view`)
|
|
71
|
+
: getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=view`);
|
|
72
|
+
apiServices.get(endpoint).then((accessCheck) => setHasViewPermission(accessCheck.result));
|
|
64
73
|
}
|
|
65
74
|
};
|
|
66
75
|
const isFile = (doc) => doc instanceof File;
|
|
@@ -78,7 +87,12 @@ export const DocumentList = (props) => {
|
|
|
78
87
|
: savedDocuments?.find((savedDocument) => savedDocument.id === doc.id)?.contentType;
|
|
79
88
|
if (!isFile(doc)) {
|
|
80
89
|
try {
|
|
81
|
-
|
|
90
|
+
// For 'file' type properties, use /files/{id}/content endpoint
|
|
91
|
+
// For 'document' type properties, use /objects/{id}/instances/{id}/documents/{id}/content endpoint
|
|
92
|
+
const contentEndpoint = property.type === 'file'
|
|
93
|
+
? getPrefixedUrl(`/files/${doc.id}/content`)
|
|
94
|
+
: getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/${doc.id}/content`);
|
|
95
|
+
const documentResponse = await apiServices.get(contentEndpoint, { responseType: 'blob' });
|
|
82
96
|
const blob = new Blob([documentResponse], { type: contentType });
|
|
83
97
|
url = window.URL.createObjectURL(blob);
|
|
84
98
|
}
|
|
@@ -25,6 +25,8 @@ export function determineComponentType(properties, parameter) {
|
|
|
25
25
|
}
|
|
26
26
|
case 'document':
|
|
27
27
|
return 'Document';
|
|
28
|
+
case 'file':
|
|
29
|
+
return 'File';
|
|
28
30
|
case 'array':
|
|
29
31
|
return 'MultiSelect';
|
|
30
32
|
case 'number':
|
|
@@ -161,7 +163,7 @@ export function convertFormToComponents(entries, parameters, object) {
|
|
|
161
163
|
rows: displayOptions?.rowCount,
|
|
162
164
|
key: parameter.id,
|
|
163
165
|
type,
|
|
164
|
-
multiple: ['array', 'document'].includes(parameter.type),
|
|
166
|
+
multiple: ['array', 'document', 'file'].includes(parameter.type),
|
|
165
167
|
data: {
|
|
166
168
|
values: entry.type === 'input' ? entry.enumWithLabels : undefined,
|
|
167
169
|
},
|
|
@@ -227,19 +229,19 @@ export function convertFormToComponents(entries, parameters, object) {
|
|
|
227
229
|
max: ['integer', 'number'].includes(parameter.type ?? '')
|
|
228
230
|
? parameter.validation?.maximum
|
|
229
231
|
: undefined,
|
|
230
|
-
minDocuments: parameter.type
|
|
232
|
+
minDocuments: ['document', 'file'].includes(parameter.type ?? '')
|
|
231
233
|
? parameter.validation?.minDocuments
|
|
232
234
|
: undefined,
|
|
233
|
-
maxDocuments: parameter.type
|
|
235
|
+
maxDocuments: ['document', 'file'].includes(parameter.type ?? '')
|
|
234
236
|
? parameter.validation?.maxDocuments
|
|
235
237
|
: undefined,
|
|
236
|
-
allowedFileExtensions: parameter.type
|
|
238
|
+
allowedFileExtensions: ['document', 'file'].includes(parameter.type ?? '')
|
|
237
239
|
? parameter.validation?.allowedFileExtensions
|
|
238
240
|
: undefined,
|
|
239
|
-
maxSizeInKB: parameter.type
|
|
241
|
+
maxSizeInKB: ['document', 'file'].includes(parameter.type ?? '')
|
|
240
242
|
? parameter.validation?.maxSizeInKB
|
|
241
243
|
: undefined,
|
|
242
|
-
customMessage: ['integer', 'number', 'date', 'time', 'document'].includes(parameter.type ?? '')
|
|
244
|
+
customMessage: ['integer', 'number', 'date', 'time', 'document', 'file'].includes(parameter.type ?? '')
|
|
243
245
|
? parameter.validation
|
|
244
246
|
?.errorMessage
|
|
245
247
|
: '',
|
|
@@ -254,7 +256,9 @@ export function convertFormToComponents(entries, parameters, object) {
|
|
|
254
256
|
conditional: convertVisibilityToConditional(displayOptions?.visibility),
|
|
255
257
|
viewLayout: displayOptions?.viewLayout,
|
|
256
258
|
strictlyTrue: parameter.type === 'boolean' && parameter.strictlyTrue,
|
|
257
|
-
documentMetadata: parameter.type
|
|
259
|
+
documentMetadata: ['document', 'file'].includes(parameter.type ?? '')
|
|
260
|
+
? entry.documentMetadata
|
|
261
|
+
: undefined,
|
|
258
262
|
};
|
|
259
263
|
}
|
|
260
264
|
})
|
|
@@ -454,7 +458,7 @@ export function getMiddleInstance(instanceId, property, middleObjectInstances) {
|
|
|
454
458
|
// The following function is used to prefix the URL with the appropriate path.
|
|
455
459
|
export function getPrefixedUrl(url) {
|
|
456
460
|
const wcsMatchers = ['/apps', '/pages', '/widgets'];
|
|
457
|
-
const dataMatchers = ['/objects', '/correspondenceTemplates', '/documents', '/payments', '/locations'];
|
|
461
|
+
const dataMatchers = ['/objects', '/correspondenceTemplates', '/documents', '/files', '/payments', '/locations'];
|
|
458
462
|
const signalrMatchers = ['/hubs'];
|
|
459
463
|
const accessManagementMatchers = ['/users'];
|
|
460
464
|
const workflowMatchers = ['/workflows'];
|
|
@@ -664,7 +668,7 @@ formComponents, allCriteriaInputs, instance, objectPropertyInputProps, associate
|
|
|
664
668
|
properties,
|
|
665
669
|
type: `${readOnly ? 'ViewOnly' : ''}${component.type}`,
|
|
666
670
|
readOnly: component.readOnly || readOnly || property?.formula,
|
|
667
|
-
multiple: ['array', 'document'].includes(property.type),
|
|
671
|
+
multiple: ['array', 'document', 'file'].includes(property.type),
|
|
668
672
|
instance: instance,
|
|
669
673
|
fromFormBuilder: true,
|
|
670
674
|
apiServices: objectPropertyInputProps?.apiServices,
|
|
@@ -708,7 +712,7 @@ formComponents, allCriteriaInputs, instance, objectPropertyInputProps, associate
|
|
|
708
712
|
property,
|
|
709
713
|
associatedObject,
|
|
710
714
|
readOnly: component.readOnly || readOnly || property?.formula,
|
|
711
|
-
multiple: ['array', 'document'].includes(property.type),
|
|
715
|
+
multiple: ['array', 'document', 'file'].includes(property.type),
|
|
712
716
|
instance: instance,
|
|
713
717
|
fromFormBuilder: true,
|
|
714
718
|
apiServices: objectPropertyInputProps?.apiServices,
|
|
@@ -1115,7 +1119,7 @@ export const buildComponentPropsFromObjectProperties = (properties, objectId, in
|
|
|
1115
1119
|
key: property.id,
|
|
1116
1120
|
label: property.name,
|
|
1117
1121
|
inputMask: property.mask,
|
|
1118
|
-
multiple: ['array', 'document'].includes(property.type),
|
|
1122
|
+
multiple: ['array', 'document', 'file'].includes(property.type),
|
|
1119
1123
|
readOnly: !hasActionPermissions || readOnly || !!property?.formula,
|
|
1120
1124
|
instance: instance,
|
|
1121
1125
|
validate: {
|
|
@@ -1155,7 +1159,7 @@ export const buildComponentPropsFromObjectProperties = (properties, objectId, in
|
|
|
1155
1159
|
: 'Property is not in a valid format',
|
|
1156
1160
|
})),
|
|
1157
1161
|
}),
|
|
1158
|
-
...(property.type
|
|
1162
|
+
...(['document', 'file'].includes(property.type) &&
|
|
1159
1163
|
property.validation && {
|
|
1160
1164
|
minDocuments: property.validation.minDocuments,
|
|
1161
1165
|
maxDocuments: property.validation.maxDocuments,
|
|
@@ -27,7 +27,11 @@ export const DocumentList = (props) => {
|
|
|
27
27
|
const { handleChange, onAutosave, id, canUpdateProperty, value: documents, setSnackbarError } = props;
|
|
28
28
|
const apiServices = useApiServices();
|
|
29
29
|
const { fetchedOptions, setFetchedOptions, object, instance } = useFormContext();
|
|
30
|
+
// Determine property type once at component level
|
|
31
|
+
const propertyType = object?.properties?.find((p) => p.id === id)?.type;
|
|
32
|
+
const isFileType = propertyType === 'file';
|
|
30
33
|
const [hasViewPermission, setHasViewPermission] = useState(fetchedOptions[`${id}ViewPermission`] ?? true);
|
|
34
|
+
// savedDocuments is either FileInstance[] or DocumentType[], never a mix
|
|
31
35
|
const [savedDocuments, setSavedDocuments] = useState(fetchedOptions[`${id}SavedDocuments`]);
|
|
32
36
|
useEffect(() => {
|
|
33
37
|
const currentValue = instance?.[id];
|
|
@@ -49,13 +53,19 @@ export const DocumentList = (props) => {
|
|
|
49
53
|
}
|
|
50
54
|
}, [fetchedOptions]);
|
|
51
55
|
const getDocuments = (currentDocumentIds, shouldRetry = true) => {
|
|
52
|
-
|
|
56
|
+
// For 'file' type properties, fetch sys__file instances directly
|
|
57
|
+
// For 'document' type properties, fetch attachment documents
|
|
58
|
+
const endpoint = isFileType
|
|
59
|
+
? getPrefixedUrl(`/objects/sys__file/instances`)
|
|
60
|
+
: getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents`);
|
|
61
|
+
apiServices.get(endpoint, {
|
|
53
62
|
params: { filter: { where: { id: { inq: currentDocumentIds } } } },
|
|
54
63
|
}, (error, docs) => {
|
|
55
64
|
// There is a short delay between when a document is uploaded and when
|
|
56
65
|
// it is indexed. Therefore, try again if documents are not found.
|
|
57
66
|
if (shouldRetry &&
|
|
58
|
-
(!docs ||
|
|
67
|
+
(!docs ||
|
|
68
|
+
currentDocumentIds.some((docId) => !docs.find((doc) => docId === doc.id)))) {
|
|
59
69
|
setTimeout(() => getDocuments(currentDocumentIds, false), 2000);
|
|
60
70
|
}
|
|
61
71
|
else if (error) {
|
|
@@ -114,7 +124,12 @@ export const DocumentList = (props) => {
|
|
|
114
124
|
: savedDocuments?.find((savedDocument) => savedDocument.id === doc.id)?.contentType;
|
|
115
125
|
if (!isFile(doc)) {
|
|
116
126
|
try {
|
|
117
|
-
|
|
127
|
+
// Determine property type to use the correct endpoint
|
|
128
|
+
const propertyType = object?.properties?.find((p) => p.id === id)?.type;
|
|
129
|
+
const contentEndpoint = propertyType === 'file'
|
|
130
|
+
? getPrefixedUrl(`/files/${doc.id}/content`)
|
|
131
|
+
: getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/${doc.id}/content`);
|
|
132
|
+
const documentResponse = await apiServices.get(contentEndpoint, { responseType: 'blob' });
|
|
118
133
|
const blob = new Blob([documentResponse], { type: contentType });
|
|
119
134
|
url = window.URL.createObjectURL(blob);
|
|
120
135
|
}
|
|
@@ -85,8 +85,8 @@ export function RecursiveEntryRenderer(props) {
|
|
|
85
85
|
}
|
|
86
86
|
else if (fieldDefinition.type === 'object') {
|
|
87
87
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
88
|
-
React.createElement(ObjectPropertyInput, { relatedObjectId: !fieldDefinition.objectId ? display?.relatedObjectId : fieldDefinition.objectId, fieldDefinition: fieldDefinition, id: entryId, mode: display?.mode || 'default', error: !!errors?.[entryId], displayOption: display?.relatedObjectDisplay || 'dialogBox', initialValue: fieldValue, readOnly: entry.type === 'readonlyField', filter:
|
|
89
|
-
? updateCriteriaInputs(validation.criteria, getValues(), userAccount
|
|
88
|
+
React.createElement(ObjectPropertyInput, { relatedObjectId: !fieldDefinition.objectId ? display?.relatedObjectId : fieldDefinition.objectId, fieldDefinition: fieldDefinition, id: entryId, mode: display?.mode || 'default', error: !!errors?.[entryId], displayOption: display?.relatedObjectDisplay || 'dialogBox', initialValue: fieldValue, readOnly: entry.type === 'readonlyField', filter: validation?.criteria
|
|
89
|
+
? updateCriteriaInputs(validation.criteria, getValues(), userAccount)
|
|
90
90
|
: undefined, sortBy: typeof display?.defaultValue === 'object' && 'sortBy' in display.defaultValue
|
|
91
91
|
? display?.defaultValue.sortBy
|
|
92
92
|
: undefined, orderBy: typeof display?.defaultValue === 'object' && 'orderBy' in display.defaultValue
|
|
@@ -104,9 +104,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
104
104
|
if (middleObject && !isEmpty(middleObject)) {
|
|
105
105
|
return (initialMiddleObjectInstances && (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
106
106
|
React.createElement(DropdownRepeatableField, { initialMiddleObjectInstances: fetchedOptions[`${entryId}MiddleObjectInstances`] ||
|
|
107
|
-
initialMiddleObjectInstances, fieldDefinition: fieldDefinition, id: entryId, middleObject: middleObject, readOnly: entry.type === 'readonlyField', criteria:
|
|
108
|
-
? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
|
|
109
|
-
: undefined, hasDescription: !!display?.description }))));
|
|
107
|
+
initialMiddleObjectInstances, fieldDefinition: fieldDefinition, id: entryId, middleObject: middleObject, readOnly: entry.type === 'readonlyField', criteria: validation?.criteria, hasDescription: !!display?.description }))));
|
|
110
108
|
}
|
|
111
109
|
else {
|
|
112
110
|
// when in the builder preview, the middle object won't be fetched so instead show an empty field
|
|
@@ -118,9 +116,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
118
116
|
}
|
|
119
117
|
else {
|
|
120
118
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
121
|
-
React.createElement(RepeatableField, { fieldDefinition: fieldDefinition, canUpdateProperty: entry.type !== 'readonlyField', criteria:
|
|
122
|
-
? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
|
|
123
|
-
: undefined, viewLayout: display?.viewLayout, entry: entry })));
|
|
119
|
+
React.createElement(RepeatableField, { fieldDefinition: fieldDefinition, canUpdateProperty: entry.type !== 'readonlyField', criteria: validation?.criteria, viewLayout: display?.viewLayout, entry: entry })));
|
|
124
120
|
}
|
|
125
121
|
}
|
|
126
122
|
else if (fieldDefinition.type === 'richText') {
|
|
@@ -134,7 +130,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
134
130
|
});
|
|
135
131
|
}, readOnly: entry.type === 'readonlyField', placeholder: display?.placeholder, error: !!errors?.[entryId], errorMessage: errors?.[entryId]?.message, isMultiLineText: !!display?.rowCount, rows: display?.rowCount, size: fieldHeight }))));
|
|
136
132
|
}
|
|
137
|
-
else if (fieldDefinition.type === 'document') {
|
|
133
|
+
else if (fieldDefinition.type === 'document' || fieldDefinition.type === 'file') {
|
|
138
134
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
139
135
|
React.createElement(Document, { id: entryId, error: !!errors?.[entryId], value: fieldValue, canUpdateProperty: !(entry.type === 'readonlyField'), hasDescription: !!display?.description, validate: validation })));
|
|
140
136
|
}
|
|
@@ -27,6 +27,14 @@ export type Document = {
|
|
|
27
27
|
};
|
|
28
28
|
versionId?: string;
|
|
29
29
|
};
|
|
30
|
+
export type FileInstance = {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
contentType: string;
|
|
34
|
+
size: number;
|
|
35
|
+
dateUploaded: string;
|
|
36
|
+
uploadedBy: string;
|
|
37
|
+
};
|
|
30
38
|
export type SimpleEditorProps = {
|
|
31
39
|
id: string;
|
|
32
40
|
value: string;
|
|
@@ -40,7 +40,7 @@ export declare const encodePageSlug: (slug: string) => string;
|
|
|
40
40
|
export declare function getDefaultPages(parameters: InputParameter[], defaultPages: Record<string, string> | undefined, findDefaultPageSlugFor: (objectId: string) => Promise<string | undefined>): Promise<{
|
|
41
41
|
[x: string]: string;
|
|
42
42
|
}>;
|
|
43
|
-
export declare function updateCriteriaInputs(criteria: Record<string, unknown>, data: Record<string, unknown>, user?: UserAccount
|
|
43
|
+
export declare function updateCriteriaInputs(criteria: Record<string, unknown>, data: Record<string, unknown>, user?: UserAccount): Record<string, unknown>;
|
|
44
44
|
export declare function fetchCollectionData(apiServices: ApiServices, fieldDefinition: InputParameter | Property, setFetchedOptions: (newData: FieldValues) => void, instanceId?: string, fetchedOptions?: Record<string, unknown>, initialMiddleObjectInstances?: ObjectInstance[]): Promise<void>;
|
|
45
45
|
export declare const getErrorCountForSection: (section: Section | Column, errors?: FieldErrors) => number;
|
|
46
46
|
export declare const propertyToParameter: (property: Property) => InputParameter;
|
|
@@ -49,6 +49,10 @@ export declare const convertPropertiesToParams: (object: Obj) => InputParameter[
|
|
|
49
49
|
export declare function getUnnestedEntries(entries: FormEntry[]): FormEntry[];
|
|
50
50
|
export declare const isEmptyWithDefault: (fieldValue: unknown, entry: InputParameterReference | InputField, instance: Record<string, unknown> | object) => boolean | "" | 0 | undefined;
|
|
51
51
|
export declare const docProperties: Property[];
|
|
52
|
+
/**
|
|
53
|
+
* Upload files using the POST /files endpoint for sys__file objects
|
|
54
|
+
*/
|
|
55
|
+
export declare const uploadFiles: (files: (File | SavedDocumentReference)[], apiServices: ApiServices, actionId?: string, metadata?: Record<string, string>) => Promise<SavedDocumentReference[]>;
|
|
52
56
|
export declare const uploadDocuments: (files: (File | SavedDocumentReference)[], metadata: Record<string, string>, apiServices: ApiServices, instanceId: string, objectId: string) => Promise<SavedDocumentReference[]>;
|
|
53
57
|
export declare const deleteDocuments: (submittedFields: FieldValues, requestSuccess: boolean, apiServices: ApiServices, object: Obj, instance: FieldValues, action?: Action, setSnackbarError?: React.Dispatch<React.SetStateAction<{
|
|
54
58
|
showAlert: boolean;
|
|
@@ -307,13 +307,12 @@ function compileQueryValues(query, data) {
|
|
|
307
307
|
return query;
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
|
-
export function updateCriteriaInputs(criteria, data, user
|
|
310
|
+
export function updateCriteriaInputs(criteria, data, user) {
|
|
311
311
|
const dataSet = {
|
|
312
312
|
input: {
|
|
313
313
|
...data,
|
|
314
314
|
},
|
|
315
315
|
user: user,
|
|
316
|
-
instance,
|
|
317
316
|
};
|
|
318
317
|
const compiledQuery = compileQueryValues(parseMongoDB(criteria), dataSet);
|
|
319
318
|
// The "compiledQueryValues" function filters out rules that have a value of "undefined".
|
|
@@ -469,6 +468,33 @@ export const docProperties = [
|
|
|
469
468
|
type: 'string',
|
|
470
469
|
},
|
|
471
470
|
];
|
|
471
|
+
/**
|
|
472
|
+
* Upload files using the POST /files endpoint for sys__file objects
|
|
473
|
+
*/
|
|
474
|
+
export const uploadFiles = async (files, apiServices, actionId = '_create', metadata) => {
|
|
475
|
+
// Separate already uploaded files from files that need uploading
|
|
476
|
+
const alreadyUploaded = files.filter((file) => !('size' in file));
|
|
477
|
+
const filesToUpload = files.filter((file) => 'size' in file);
|
|
478
|
+
// Upload all files in parallel
|
|
479
|
+
const uploadPromises = filesToUpload.map(async (file) => {
|
|
480
|
+
const formData = new FormData();
|
|
481
|
+
formData.append('file', file);
|
|
482
|
+
formData.append('actionId', actionId);
|
|
483
|
+
formData.append('objectId', 'sys__file');
|
|
484
|
+
if (metadata) {
|
|
485
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
486
|
+
formData.append(key, value);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const fileInstance = await apiServices.post(getPrefixedUrl(`/files`), formData);
|
|
490
|
+
return {
|
|
491
|
+
id: fileInstance.id,
|
|
492
|
+
name: fileInstance.name,
|
|
493
|
+
};
|
|
494
|
+
});
|
|
495
|
+
const uploadedFiles = await Promise.all(uploadPromises);
|
|
496
|
+
return [...alreadyUploaded, ...uploadedFiles];
|
|
497
|
+
};
|
|
472
498
|
export const uploadDocuments = async (files, metadata, apiServices, instanceId, objectId) => {
|
|
473
499
|
const allDocuments = [];
|
|
474
500
|
const formData = new FormData();
|
|
@@ -494,8 +520,8 @@ export const uploadDocuments = async (files, metadata, apiServices, instanceId,
|
|
|
494
520
|
};
|
|
495
521
|
export const deleteDocuments = async (submittedFields, requestSuccess, apiServices, object, instance, action, setSnackbarError) => {
|
|
496
522
|
const documentProperties = action?.parameters
|
|
497
|
-
? action.parameters.filter((param) => param.type
|
|
498
|
-
: object?.properties?.filter((prop) => prop.type
|
|
523
|
+
? action.parameters.filter((param) => ['document', 'file'].includes(param.type))
|
|
524
|
+
: object?.properties?.filter((prop) => ['document', 'file'].includes(prop.type));
|
|
499
525
|
for (const docProperty of documentProperties ?? []) {
|
|
500
526
|
const savedValue = submittedFields[docProperty.id];
|
|
501
527
|
const originalValue = instance?.[docProperty.id];
|
|
@@ -504,14 +530,18 @@ export const deleteDocuments = async (submittedFields, requestSuccess, apiServic
|
|
|
504
530
|
: (savedValue?.filter((file) => !originalValue?.some((f) => f.id === file.id)) ?? []);
|
|
505
531
|
for (const doc of documentsToRemove) {
|
|
506
532
|
try {
|
|
507
|
-
|
|
533
|
+
// Use different endpoints based on property type
|
|
534
|
+
const deleteEndpoint = docProperty.type === 'file'
|
|
535
|
+
? getPrefixedUrl(`/files/${doc.id}`)
|
|
536
|
+
: getPrefixedUrl(`/objects/${object.id}/instances/${instance.id}/documents/${doc.id}`);
|
|
537
|
+
await apiServices?.delete(deleteEndpoint);
|
|
508
538
|
}
|
|
509
539
|
catch (error) {
|
|
510
540
|
if (error) {
|
|
511
541
|
setSnackbarError &&
|
|
512
542
|
setSnackbarError({
|
|
513
543
|
showAlert: true,
|
|
514
|
-
message: `An error occurred while removing document '${doc.name}'`,
|
|
544
|
+
message: `An error occurred while removing ${docProperty.type === 'file' ? 'file' : 'document'} '${doc.name}'`,
|
|
515
545
|
isError: true,
|
|
516
546
|
});
|
|
517
547
|
}
|
|
@@ -543,12 +573,19 @@ export async function formatSubmission(submission, apiServices, objectId, instan
|
|
|
543
573
|
// Only upload if array contains File instances (not SavedDocumentReference)
|
|
544
574
|
const fileInArray = value.some((item) => item instanceof File);
|
|
545
575
|
if (fileInArray && instanceId && apiServices && objectId) {
|
|
576
|
+
// Determine property type from the entry
|
|
577
|
+
const propertyType = entry?.input?.type;
|
|
546
578
|
try {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
579
|
+
// Use uploadFiles for 'file' type, uploadDocuments for 'document' type
|
|
580
|
+
const uploadedDocuments = propertyType === 'file'
|
|
581
|
+
? await uploadFiles(value, apiServices, '_create', {
|
|
582
|
+
...entry?.documentMetadata,
|
|
583
|
+
})
|
|
584
|
+
: await uploadDocuments(value, {
|
|
585
|
+
type: '',
|
|
586
|
+
view_permission: '',
|
|
587
|
+
...entry?.documentMetadata,
|
|
588
|
+
}, apiServices, instanceId, objectId);
|
|
552
589
|
submission[key] = uploadedDocuments;
|
|
553
590
|
}
|
|
554
591
|
catch (err) {
|
|
@@ -556,7 +593,7 @@ export async function formatSubmission(submission, apiServices, objectId, instan
|
|
|
556
593
|
setSnackbarError &&
|
|
557
594
|
setSnackbarError({
|
|
558
595
|
showAlert: true,
|
|
559
|
-
message: `An error occurred while uploading associated documents`,
|
|
596
|
+
message: `An error occurred while uploading associated ${propertyType === 'file' ? 'files' : 'documents'}`,
|
|
560
597
|
isError: true,
|
|
561
598
|
});
|
|
562
599
|
}
|
|
@@ -129,8 +129,8 @@ function ViewOnlyEntryRenderer(props) {
|
|
|
129
129
|
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
130
130
|
id: entry.uniqueId, value: fieldValue, format: "rtf", disabled: true, rows: display?.rowCount, hasError: false })) : (React.createElement(Typography, { variant: "body1", key: entryId, sx: { height: '24px' } }, fieldValue))));
|
|
131
131
|
}
|
|
132
|
-
else if (fieldDefinition.type === 'document') {
|
|
133
|
-
return (React.createElement(FieldWrapper, { inputId: entryId, inputType:
|
|
132
|
+
else if (fieldDefinition.type === 'document' || fieldDefinition.type === 'file') {
|
|
133
|
+
return (React.createElement(FieldWrapper, { inputId: entryId, inputType: fieldDefinition.type, label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, prefix: display?.prefix, suffix: display?.suffix, viewOnly: true },
|
|
134
134
|
React.createElement(Document, { id: entryId, error: false, value: fieldValue, canUpdateProperty: false })));
|
|
135
135
|
}
|
|
136
136
|
else if (fieldDefinition.type === 'collection') {
|