@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.
@@ -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 && (action?.type !== 'create' || prop.type !== 'document'));
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 === 'document')
288
- : object?.properties?.filter((prop) => prop.type === 'document');
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 === 'document')
334
- : object?.properties?.filter((prop) => prop.type === 'document');
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
- await apiServices?.delete(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/${doc.id}`));
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({ message: `An error occurred while removing document '${doc.name}'` });
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(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=update`))
49
+ .get(endpoint)
45
50
  .then((accessCheck) => setHasUpdatePermission(accessCheck.result))
46
51
  .catch(() => setHasUpdatePermission(false));
47
52
  }
@@ -36,13 +36,19 @@ export const DocumentList = (props) => {
36
36
  }
37
37
  }, [property]);
38
38
  const getDocuments = (currentDocumentIds, shouldRetry = true) => {
39
- apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents`), {
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 || currentDocumentIds.some((docId) => !docs.find((doc) => docId === doc.id)))) {
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
- apiServices
62
- .get(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=view`))
63
- .then((accessCheck) => setHasViewPermission(accessCheck.result));
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
- const documentResponse = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/${doc.id}/content`), { responseType: 'blob' });
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 === 'document'
232
+ minDocuments: ['document', 'file'].includes(parameter.type ?? '')
231
233
  ? parameter.validation?.minDocuments
232
234
  : undefined,
233
- maxDocuments: parameter.type === 'document'
235
+ maxDocuments: ['document', 'file'].includes(parameter.type ?? '')
234
236
  ? parameter.validation?.maxDocuments
235
237
  : undefined,
236
- allowedFileExtensions: parameter.type === 'document'
238
+ allowedFileExtensions: ['document', 'file'].includes(parameter.type ?? '')
237
239
  ? parameter.validation?.allowedFileExtensions
238
240
  : undefined,
239
- maxSizeInKB: parameter.type === 'document'
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 === 'document' ? entry.documentMetadata : undefined,
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 === 'document' &&
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
- apiServices.get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents`), {
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 || currentDocumentIds.some((docId) => !docs.find((doc) => docId === doc.id)))) {
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
- const documentResponse = await apiServices.get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/${doc.id}/content`), { responseType: 'blob' });
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: 'criteria' in validation && validation.criteria
89
- ? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
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: 'criteria' in validation && validation.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: 'criteria' in validation && validation.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, instance?: Record<string, unknown>): Record<string, unknown>;
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, instance) {
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 === 'document')
498
- : object?.properties?.filter((prop) => prop.type === 'document');
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
- await apiServices?.delete(getPrefixedUrl(`/objects/${object.id}/instances/${instance.id}/documents/${doc.id}`));
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
- const uploadedDocuments = await uploadDocuments(value, {
548
- type: '',
549
- view_permission: '',
550
- ...entry?.documentMetadata,
551
- }, apiServices, instanceId, objectId);
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: 'document', label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, prefix: display?.prefix, suffix: display?.suffix, viewOnly: true },
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') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.12.0-dev.1",
3
+ "version": "1.12.0-dev.3",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",