@evoke-platform/ui-components 1.15.0 → 1.16.0

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 (46) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.d.ts +8 -4
  2. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +242 -142
  3. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +189 -67
  4. package/dist/published/components/custom/CriteriaBuilder/PropertyTree.d.ts +6 -6
  5. package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +12 -25
  6. package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.d.ts +4 -5
  7. package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.js +34 -22
  8. package/dist/published/components/custom/CriteriaBuilder/types.d.ts +2 -11
  9. package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +6 -34
  10. package/dist/published/components/custom/CriteriaBuilder/utils.js +18 -89
  11. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
  12. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +6 -3
  13. package/dist/published/components/custom/Form/utils.d.ts +1 -0
  14. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
  15. package/dist/published/components/custom/FormV2/FormRenderer.js +2 -8
  16. package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +4 -0
  17. package/dist/published/components/custom/FormV2/FormRendererContainer.js +229 -126
  18. package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +1 -1
  19. package/dist/published/components/custom/FormV2/components/DefaultValues.js +60 -89
  20. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +1 -1
  21. package/dist/published/components/custom/FormV2/components/FormContext.js +0 -1
  22. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +1 -0
  23. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +43 -16
  24. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +2 -2
  25. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +3 -2
  26. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +3 -2
  27. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +44 -11
  28. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +4 -3
  29. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +41 -29
  30. package/dist/published/components/custom/FormV2/components/FormFieldTypes/FileContent.d.ts +12 -0
  31. package/dist/published/components/custom/FormV2/components/FormFieldTypes/FileContent.js +197 -0
  32. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +2 -4
  33. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +14 -0
  34. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +6 -2
  35. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +16 -13
  36. package/dist/published/components/custom/FormV2/components/types.d.ts +6 -1
  37. package/dist/published/components/custom/FormV2/components/utils.d.ts +10 -8
  38. package/dist/published/components/custom/FormV2/components/utils.js +168 -82
  39. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +6 -1
  40. package/dist/published/components/custom/index.d.ts +1 -0
  41. package/dist/published/index.d.ts +1 -1
  42. package/dist/published/stories/CriteriaBuilder.stories.js +70 -22
  43. package/dist/published/stories/FormRenderer.stories.d.ts +6 -3
  44. package/dist/published/stories/FormRendererContainer.stories.d.ts +20 -0
  45. package/dist/published/theme/hooks.d.ts +3 -3
  46. package/package.json +3 -1
@@ -1,7 +1,7 @@
1
- import { isArray, isEmpty, uniq } from 'lodash';
1
+ import { isArray, isEmpty, isObject, uniq } from 'lodash';
2
2
  import { DateTime } from 'luxon';
3
- import { getEntryId, getPrefixedUrl, isAddressProperty } from './utils';
4
- export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, formValues, updatedRelatedObjectValue) {
3
+ import { getEntryId, getPrefixedUrl, isAddressProperty, transformToWhere, updateCriteriaInputs } from './utils';
4
+ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, formValues, updatedRelatedObjectValue, updatedRelatedObjectParamId) {
5
5
  const updates = [];
6
6
  const parameter = parameters.find((param) => param.id === fieldId);
7
7
  const defaultValue = entry.display?.defaultValue;
@@ -14,7 +14,6 @@ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldV
14
14
  const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
15
15
  const groups = regex.exec(item)?.groups;
16
16
  if (groups?.relatedObjectProperty && groups?.nestedProperty) {
17
- const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
18
17
  let relatedObjectInstance = updatedRelatedObjectValue;
19
18
  if (!relatedObjectInstance && !isEmpty(formValues)) {
20
19
  relatedObjectInstance = formValues[groups.relatedObjectProperty];
@@ -28,30 +27,14 @@ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldV
28
27
  ]);
29
28
  updates.push({ fieldId, fieldValue });
30
29
  }
31
- else if (relatedObjectInstance?.id && relatedObjectParameter) {
32
- let relatedObjectId = relatedObjectParameter.objectId;
33
- if (!relatedObjectId) {
34
- const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
35
- relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
36
- }
37
- const instance = await new Promise((resolve) => {
38
- apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
39
- if (error) {
40
- console.error(error);
41
- return resolve(undefined);
42
- }
43
- resolve(instance);
44
- });
45
- });
46
- if (instance) {
47
- fieldValue = uniq([
48
- ...staticValues,
49
- ...(isArray(instance[groups.nestedProperty])
50
- ? instance[groups.nestedProperty]
51
- : []),
52
- ]);
53
- updates.push({ fieldId, fieldValue });
54
- }
30
+ else if (relatedObjectInstance) {
31
+ fieldValue = uniq([
32
+ ...staticValues,
33
+ ...(isArray(relatedObjectInstance[groups.nestedProperty])
34
+ ? relatedObjectInstance[groups.nestedProperty]
35
+ : []),
36
+ ]);
37
+ updates.push({ fieldId, fieldValue });
55
38
  }
56
39
  else {
57
40
  updates.push({ fieldId, fieldValue: staticValues });
@@ -65,42 +48,23 @@ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldV
65
48
  }
66
49
  else if (typeof defaultValue === 'string' && /^{{.*}}$/.test(defaultValue)) {
67
50
  if (isAddressProperty(fieldId)) {
68
- const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<addressProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedAddressProperty>line1|line2|city|county|state|zipCode)}}$/;
51
+ const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<addressProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedAddressProperty>line1|line2|city|county|state|zipCode|country)}}$/;
69
52
  const groups = regex.exec(defaultValue)?.groups;
70
53
  if (groups?.relatedObjectProperty && groups?.addressProperty && groups?.nestedAddressProperty) {
71
- const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
72
54
  let relatedObjectInstance = updatedRelatedObjectValue;
73
55
  if (!relatedObjectInstance && !isEmpty(formValues)) {
74
56
  relatedObjectInstance = formValues[groups.relatedObjectProperty];
75
57
  }
76
- if (updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty]) {
58
+ if (updatedRelatedObjectValue === null) {
59
+ updates.push({ fieldId, fieldValue: '' });
60
+ }
61
+ else if (updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty]) {
77
62
  fieldValue = updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty];
78
63
  updates.push({ fieldId, fieldValue });
79
64
  }
80
- else if (relatedObjectInstance?.id && relatedObjectParameter) {
81
- let relatedObjectId = relatedObjectParameter.objectId;
82
- if (!relatedObjectId) {
83
- const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
84
- relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
85
- }
86
- const instance = await new Promise((resolve) => {
87
- apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
88
- if (error) {
89
- console.error(error);
90
- return resolve(undefined);
91
- }
92
- resolve(instance);
93
- });
94
- });
95
- // Clear dependent fields only if value is explicitly null (user cleared it).
96
- // If updatedRelatedObjectValue is undefined (not triggered by onChange), use the value from the instance.
97
- if (updatedRelatedObjectValue === null) {
98
- updates.push({ fieldId, fieldValue: '' });
99
- }
100
- else if (instance) {
101
- fieldValue = instance?.[groups.addressProperty]?.[groups.nestedAddressProperty];
102
- updates.push({ fieldId, fieldValue });
103
- }
65
+ else if (relatedObjectInstance) {
66
+ fieldValue = relatedObjectInstance?.[groups.addressProperty]?.[groups.nestedAddressProperty];
67
+ updates.push({ fieldId, fieldValue });
104
68
  }
105
69
  }
106
70
  }
@@ -108,44 +72,44 @@ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldV
108
72
  const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
109
73
  const groups = regex.exec(defaultValue)?.groups;
110
74
  if (groups?.relatedObjectProperty && groups?.nestedProperty) {
111
- const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
112
75
  let relatedObjectInstance = updatedRelatedObjectValue;
113
76
  if (!relatedObjectInstance && !isEmpty(formValues)) {
114
77
  relatedObjectInstance = formValues[groups.relatedObjectProperty];
115
78
  }
116
- if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
79
+ if (updatedRelatedObjectValue === null) {
80
+ updates.push({ fieldId, fieldValue: null });
81
+ }
82
+ else if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
117
83
  fieldValue = updatedRelatedObjectValue[groups.nestedProperty];
118
84
  updates.push({ fieldId, fieldValue });
119
85
  }
120
- else if (relatedObjectInstance?.id && relatedObjectParameter) {
121
- let relatedObjectId = relatedObjectParameter.objectId;
122
- if (!relatedObjectId) {
123
- const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
124
- relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
125
- }
126
- const instance = await new Promise((resolve) => {
127
- apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
128
- if (error) {
129
- console.error(error);
130
- return resolve(undefined);
131
- }
132
- resolve(instance);
133
- });
134
- });
135
- // Clear dependent fields only if value is explicitly null (user cleared it).
136
- // If updatedRelatedObjectValue is undefined (not triggered by onChange), use the value from the instance.
137
- if (updatedRelatedObjectValue === null) {
138
- updates.push({ fieldId, fieldValue: null });
139
- }
140
- else if (instance) {
141
- fieldValue = instance?.[groups.nestedProperty] || null;
142
- updates.push({ fieldId, fieldValue });
143
- }
86
+ else if (relatedObjectInstance) {
87
+ fieldValue = relatedObjectInstance[groups.nestedProperty] || null;
88
+ updates.push({ fieldId, fieldValue });
144
89
  }
145
90
  }
146
91
  }
147
92
  // all other default values set here
148
93
  }
94
+ else if (isObject(defaultValue) && 'criteria' in defaultValue && !isEmpty(defaultValue.criteria)) {
95
+ const criteria = defaultValue.criteria;
96
+ const evaluatedCriteria = updateCriteriaInputs(criteria, { ...formValues, [updatedRelatedObjectParamId || '']: updatedRelatedObjectValue }, userAccount);
97
+ try {
98
+ const instances = await apiServices.get(getPrefixedUrl(`/objects/${parameter?.objectId || entry.display?.relatedObjectId}/instances`), {
99
+ params: {
100
+ filter: {
101
+ where: transformToWhere(evaluatedCriteria),
102
+ limit: 1,
103
+ },
104
+ },
105
+ });
106
+ updates.push({ fieldId, fieldValue: instances[0] ?? null });
107
+ }
108
+ catch (error) {
109
+ updates.push({ fieldId, fieldValue: null });
110
+ console.error(error);
111
+ }
112
+ }
149
113
  else if (parameter?.type !== 'object') {
150
114
  let updatedValue = defaultValue;
151
115
  // handles current default values ie: "Current logged in user", "Today" etc.
@@ -175,38 +139,45 @@ export async function processValueUpdate(unnestedEntries, parameters, updatedRel
175
139
  const parameterId = getEntryId(entry);
176
140
  if (!parameterId)
177
141
  return [];
142
+ if (parameterId === changedEntryId) {
143
+ continue;
144
+ }
145
+ const defaultValue = entry.display.defaultValue;
178
146
  if (isAddressProperty(parameterId)) {
179
- const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<addressProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedAddressProperty>line1|line2|city|county|state|zipCode)}}$/;
147
+ const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<addressProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedAddressProperty>line1|line2|city|county|state|zipCode|country)}}$/;
180
148
  const groups = regex.exec(entry.display.defaultValue)?.groups;
181
149
  const [addressObject, addressField] = parameterId.split('.');
182
150
  if (groups?.relatedObjectProperty &&
183
151
  groups?.addressProperty &&
184
152
  groups?.nestedAddressProperty &&
185
153
  changedEntryId === groups.relatedObjectProperty) {
186
- const result = await evalDefaultVals(parameters, unnestedEntries, entry, formValues?.[addressObject]?.[addressField], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
154
+ const result = await evalDefaultVals(parameters, entry, formValues?.[addressObject]?.[addressField], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue, changedEntryId);
187
155
  updates.push(...result);
188
156
  }
189
157
  }
190
- else if (isArray(entry.display.defaultValue) &&
191
- entry.display.defaultValue.some((item) => /^{{.*}}$/.test(item))) {
192
- for (const item of entry.display.defaultValue.filter((item) => /^{{.*}}$/.test(item))) {
158
+ else if (isArray(defaultValue) && defaultValue.some((item) => /^{{.*}}$/.test(item))) {
159
+ for (const item of defaultValue.filter((item) => /^{{.*}}$/.test(item))) {
193
160
  const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
194
161
  const groups = regex.exec(item)?.groups;
195
162
  if (groups?.relatedObjectProperty &&
196
163
  groups?.nestedProperty &&
197
164
  changedEntryId === groups.relatedObjectProperty) {
198
- const result = await evalDefaultVals(parameters, unnestedEntries, entry, entry.display.defaultValue, parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
165
+ const result = await evalDefaultVals(parameters, entry, defaultValue, parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue, changedEntryId);
199
166
  updates.push(...result);
200
167
  }
201
168
  }
202
169
  }
170
+ else if (isObject(defaultValue) && 'criteria' in defaultValue && !isEmpty(defaultValue.criteria)) {
171
+ const result = await evalDefaultVals(parameters, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue, changedEntryId);
172
+ updates.push(...result);
173
+ }
203
174
  else {
204
175
  const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
205
- const groups = regex.exec(entry.display.defaultValue)?.groups;
176
+ const groups = regex.exec(defaultValue)?.groups;
206
177
  if (groups?.relatedObjectProperty &&
207
178
  groups?.nestedProperty &&
208
179
  changedEntryId === groups.relatedObjectProperty) {
209
- const result = await evalDefaultVals(parameters, unnestedEntries, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
180
+ const result = await evalDefaultVals(parameters, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue, changedEntryId);
210
181
  updates.push(...result);
211
182
  }
212
183
  }
@@ -18,11 +18,11 @@ type FormContextType = {
18
18
  handleChange?: (name: string, value: unknown) => void | Promise<void>;
19
19
  onAutosave?: (fieldId: string) => void | Promise<void>;
20
20
  fieldHeight?: 'small' | 'medium';
21
- triggerFieldReset?: boolean;
22
21
  showSubmitError?: boolean;
23
22
  associatedObject?: {
24
23
  instanceId: string;
25
24
  propertyId: string;
25
+ objectId?: string;
26
26
  };
27
27
  form?: EvokeForm;
28
28
  width: number;
@@ -10,7 +10,6 @@ export const FormContext = createContext({
10
10
  expandedSections: [],
11
11
  parameters: [],
12
12
  showSubmitError: false,
13
- triggerFieldReset: false,
14
13
  handleChange: () => { },
15
14
  fieldHeight: 'medium',
16
15
  form: {},
@@ -13,6 +13,7 @@ export type ActionDialogProps = {
13
13
  associatedObject?: {
14
14
  instanceId: string;
15
15
  propertyId: string;
16
+ objectId?: string;
16
17
  };
17
18
  };
18
19
  export declare const ActionDialog: (props: ActionDialogProps) => React.JSX.Element;
@@ -10,7 +10,7 @@ import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, Skel
10
10
  import { Box } from '../../../../../layout';
11
11
  import { getReadableQuery } from '../../../../CriteriaBuilder';
12
12
  import { retrieveCustomErrorMessage } from '../../../../Form/utils';
13
- import { convertPropertiesToParams, deleteDocuments, formatSubmission, getPrefixedUrl, transformToWhere, useFormById, } from '../../utils';
13
+ import { convertPropertiesToParams, deleteDocuments, formatSubmission, getPrefixedUrl, handleFileUpload, transformToWhere, useFormById, } from '../../utils';
14
14
  import { ActionDialog } from './ActionDialog';
15
15
  import { DocumentViewerCell } from './DocumentViewerCell';
16
16
  const styles = {
@@ -258,20 +258,34 @@ const RepeatableField = (props) => {
258
258
  : dialogType === 'update'
259
259
  ? entry.display?.updateActionId
260
260
  : entry.display?.deleteActionId));
261
+ const relatedProperty = relatedObject?.properties?.find((p) => p.id === fieldDefinition.relatedPropertyId);
261
262
  // when save is called we know that fieldDefinition is a parameter and fieldDefinition.objectId is defined
262
- input = await formatSubmission(input, apiServices, fieldDefinition.objectId, selectedInstanceId, action?.type === 'update' ? updateForm : undefined, undefined, instance?.id && fieldDefinition.relatedPropertyId
263
- ? { instanceId: instance.id, propertyId: fieldDefinition.relatedPropertyId }
263
+ input = await formatSubmission(input, apiServices, fieldDefinition.objectId, selectedInstanceId, action?.type === 'update' ? updateForm : action?.type === 'delete' ? deleteForm : createForm, undefined, instance?.id && fieldDefinition.relatedPropertyId
264
+ ? {
265
+ instanceId: instance.id,
266
+ propertyId: fieldDefinition.relatedPropertyId,
267
+ objectId: !relatedProperty?.objectId ? instance.objectId : undefined,
268
+ }
264
269
  : undefined, action?.parameters ?? (relatedObject && convertPropertiesToParams(relatedObject)));
265
- if (action?.type === 'create' && entry.display?.createActionId) {
270
+ if (action?.type === 'create' && action.id) {
266
271
  const updatedInput = {
267
272
  ...input,
268
- [fieldDefinition?.relatedPropertyId]: { id: instance?.id },
273
+ [fieldDefinition?.relatedPropertyId]: {
274
+ id: instance?.id,
275
+ objectId: !relatedProperty?.objectId ? instance?.objectId : undefined,
276
+ },
269
277
  };
270
278
  try {
271
- const instance = await apiServices.post(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/actions`), {
272
- actionId: entry.display?.createActionId,
273
- input: updatedInput,
274
- });
279
+ let instance = undefined;
280
+ if (relatedObject?.rootObjectId === 'sys__file') {
281
+ instance = await handleFileUpload(apiServices, updatedInput, action.id, fieldDefinition.objectId);
282
+ }
283
+ else {
284
+ instance = await apiServices.post(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/actions`), {
285
+ actionId: action.id,
286
+ input: updatedInput,
287
+ });
288
+ }
275
289
  queryClient.setQueryData(relatedInstancesQueryKey, (oldData) => {
276
290
  if (!oldData)
277
291
  return [instance];
@@ -293,12 +307,19 @@ const RepeatableField = (props) => {
293
307
  else {
294
308
  const relatedObjectId = relatedObject?.id;
295
309
  try {
296
- const response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${selectedInstanceId}/actions`), {
297
- actionId: `_${action?.type}`,
298
- input: omit(input, relatedObject?.properties
299
- ?.filter((property) => property.formula || property.type === 'collection')
300
- .map((property) => property.id) ?? []),
301
- });
310
+ let response = undefined;
311
+ const submission = omit(input, relatedObject?.properties
312
+ ?.filter((property) => property.formula || property.type === 'collection')
313
+ .map((property) => property.id) ?? []);
314
+ if (relatedObject?.rootObjectId === 'sys__file' && action?.id) {
315
+ response = await handleFileUpload(apiServices, submission, action.id, relatedObjectId, selectedInstanceId);
316
+ }
317
+ else {
318
+ response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${selectedInstanceId}/actions`), {
319
+ actionId: action?.id ?? `_${action?.type}`,
320
+ input: submission,
321
+ });
322
+ }
302
323
  if (response && relatedObject && instance) {
303
324
  deleteDocuments(input, !!response, apiServices, relatedObject, instance, action);
304
325
  }
@@ -491,7 +512,13 @@ const RepeatableField = (props) => {
491
512
  ? '_auto_'
492
513
  : deleteForm?.id
493
514
  : undefined, instanceId: selectedInstanceId, relatedParameter: fieldDefinition, associatedObject: instance?.id && fieldDefinition.relatedPropertyId
494
- ? { instanceId: instance.id, propertyId: fieldDefinition.relatedPropertyId }
515
+ ? {
516
+ instanceId: instance.id,
517
+ propertyId: fieldDefinition.relatedPropertyId,
518
+ objectId: !relatedObject?.properties?.find((p) => p.id === fieldDefinition.relatedPropertyId)?.objectId
519
+ ? instance.objectId
520
+ : undefined,
521
+ }
495
522
  : undefined })),
496
523
  React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({ isError: snackbarError.isError, showAlert: false }), message: snackbarError.message, error: snackbarError.isError })));
497
524
  };
@@ -7,5 +7,5 @@ type CriteriaProps = {
7
7
  error?: boolean;
8
8
  };
9
9
  type CriteriaValue = Record<string, unknown>;
10
- export default function Criteria(props: CriteriaProps): React.JSX.Element;
11
- export {};
10
+ declare const Criteria: (props: CriteriaProps) => React.JSX.Element;
11
+ export default Criteria;
@@ -6,7 +6,7 @@ import { Button, CircularProgress, Skeleton, Typography } from '../../../../core
6
6
  import { Box } from '../../../../layout';
7
7
  import CriteriaBuilder from '../../../CriteriaBuilder';
8
8
  import { addressProperties, getPrefixedUrl } from '../utils';
9
- export default function Criteria(props) {
9
+ const Criteria = (props) => {
10
10
  const { value, canUpdateProperty, fieldDefinition, error } = props;
11
11
  const apiServices = useApiServices();
12
12
  const { handleChange, onAutosave } = useFormContext();
@@ -90,4 +90,5 @@ export default function Criteria(props) {
90
90
  type: 'date-time',
91
91
  },
92
92
  ], enablePresetValues: true }))) : (React.createElement(Typography, { variant: "body2", sx: { color: '#637381' } }, "No criteria"));
93
- }
93
+ };
94
+ export default Criteria;
@@ -1,12 +1,13 @@
1
1
  import { DocumentParameterValidation } from '@evoke-platform/context';
2
2
  import React from 'react';
3
- import { SavedDocumentReference } from '../../types';
3
+ import { DocumentReference } from '../../types';
4
4
  type DocumentProps = {
5
5
  id: string;
6
+ fieldType?: 'file' | 'document';
6
7
  canUpdateProperty: boolean;
7
8
  error: boolean;
8
9
  validate?: DocumentParameterValidation;
9
- value: (File | SavedDocumentReference)[] | undefined;
10
+ value: (File | DocumentReference)[] | undefined;
10
11
  hasDescription?: boolean;
11
12
  };
12
13
  export declare const Document: (props: DocumentProps) => React.JSX.Element;
@@ -1,4 +1,4 @@
1
- import { useApiServices } from '@evoke-platform/context';
1
+ import { useApiServices, } from '@evoke-platform/context';
2
2
  import { useQuery } from '@tanstack/react-query';
3
3
  import prettyBytes from 'pretty-bytes';
4
4
  import React, { useEffect, useState } from 'react';
@@ -7,12 +7,12 @@ import { InfoRounded, UploadCloud } from '../../../../../../icons';
7
7
  import { useFormContext } from '../../../../../../theme/hooks';
8
8
  import { Skeleton, Snackbar, Typography } from '../../../../../core';
9
9
  import { Box, Grid } from '../../../../../layout';
10
- import { getPrefixedUrl } from '../../utils';
10
+ import { getEntryId, getPrefixedUrl, getUnnestedEntries, uploadFiles } from '../../utils';
11
11
  import { DocumentList } from './DocumentList';
12
12
  export const Document = (props) => {
13
- const { id, canUpdateProperty, error, value, validate, hasDescription } = props;
13
+ const { id, fieldType = 'document', canUpdateProperty, error, value, validate, hasDescription } = props;
14
14
  const apiServices = useApiServices();
15
- const { object, handleChange, onAutosave: onAutosave, instance } = useFormContext();
15
+ const { object, handleChange, onAutosave: onAutosave, instance, form } = useFormContext();
16
16
  const [snackbarError, setSnackbarError] = useState();
17
17
  const [documents, setDocuments] = useState();
18
18
  let allowedTypesMessage = '';
@@ -32,11 +32,22 @@ export const Document = (props) => {
32
32
  useEffect(() => {
33
33
  setDocuments(value);
34
34
  }, [value]);
35
+ // Find the entry to get the configured createActionId and fileObjectId
36
+ const allEntries = getUnnestedEntries(form?.entries ?? []) ?? [];
37
+ const entry = allEntries?.find((entry) => getEntryId(entry) === id);
38
+ const createActionId = entry?.display?.createActionId ?? '_create';
39
+ // Get the configured objectId from display.fileObjectId, defaulting to sys__file
40
+ const fileObjectId = entry?.display?.fileObjectId ?? 'sys__file';
41
+ // For 'file' type properties, check regular object instance permissions
42
+ // For 'document' type properties, check document attachment permissions
43
+ const endpoint = fieldType === 'file'
44
+ ? getPrefixedUrl(`/objects/${fileObjectId}/instances/checkAccess?action=execute&field=${createActionId}`)
45
+ : getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=update`);
35
46
  const { data: hasUpdatePermission = false, isLoading } = useQuery({
36
- queryKey: ['hasDocUpdatePermission', object?.id, instance?.id],
47
+ queryKey: ['hasUpdatePermission', object?.id, instance?.id, fieldType, id],
37
48
  queryFn: async () => {
38
49
  try {
39
- const accessCheck = await apiServices.get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=update`));
50
+ const accessCheck = await apiServices.get(endpoint);
40
51
  return accessCheck.result;
41
52
  }
42
53
  catch {
@@ -44,14 +55,36 @@ export const Document = (props) => {
44
55
  }
45
56
  },
46
57
  staleTime: Infinity,
47
- enabled: canUpdateProperty && !!instance?.id && !!object?.id,
58
+ // For 'file' type fields the permission endpoint only requires the object ID, so the
59
+ // query can run on create actions where no instance exists yet. For 'document' type
60
+ // fields the endpoint is instance-scoped, so instance ID is still required.
61
+ enabled: canUpdateProperty && !!object?.id && (fieldType === 'file' || !!instance?.id),
48
62
  });
49
63
  const handleUpload = async (files) => {
50
- // Store File objects in form state - they will be uploaded during autosave via formatSubmission()
51
- const newDocuments = [...(documents ?? []), ...(files ?? [])];
64
+ if (!files?.length) {
65
+ return;
66
+ }
67
+ let uploadedFiles = files;
68
+ // Get the createActionId and fileObjectId from form entry, default to '_create' and 'sys__file'
69
+ const allEntries = getUnnestedEntries(form?.entries ?? []);
70
+ const entry = allEntries?.find((entry) => getEntryId(entry) === id);
71
+ const createActionId = entry?.display?.createActionId ?? '_create';
72
+ const fileObjectId = entry?.display?.fileObjectId ?? 'sys__file';
73
+ // Immediately upload files for 'file' type properties when autosave is not enabled.
74
+ // Linking will happen upon final submission.
75
+ // If autosave is enabled, upload and linking will happen in the autosave handler.
76
+ if (fieldType === 'file' && !onAutosave) {
77
+ const { successfulUploads, errorMessage } = await uploadFiles(files, apiServices, createActionId, fileObjectId, undefined, false);
78
+ uploadedFiles = successfulUploads;
79
+ if (errorMessage) {
80
+ setSnackbarError({ message: errorMessage, type: 'error' });
81
+ }
82
+ }
83
+ // Store uploaded file references (or File objects) in form state
84
+ const newDocuments = [...(documents ?? []), ...uploadedFiles];
52
85
  setDocuments(newDocuments);
53
86
  try {
54
- handleChange && (await handleChange(id, newDocuments));
87
+ await handleChange?.(id, newDocuments);
55
88
  }
56
89
  catch (error) {
57
90
  console.error('Failed to update field:', error);
@@ -119,7 +152,7 @@ export const Document = (props) => {
119
152
  } }, validate?.maxDocuments === 1
120
153
  ? `Maximum size is ${formattedMaxSize}.`
121
154
  : `The maximum size of each document is ${formattedMaxSize}.`)))))),
122
- canUpdateProperty && isLoading ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, handleChange: handleChange, onAutosave: onAutosave, value: documents, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
155
+ canUpdateProperty && isLoading ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, fieldType: fieldType, handleChange: handleChange, onAutosave: onAutosave, value: documents, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
123
156
  React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' }),
124
157
  errors.length > 0 && (React.createElement(Box, { display: 'flex', alignItems: 'center' },
125
158
  React.createElement(InfoRounded, { sx: { fontSize: '.75rem', marginRight: '3px', color: '#D3271B' } }),
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
- import { SavedDocumentReference } from '../../types';
2
+ import { DocumentReference } from '../../types';
3
3
  type DocumentListProps = {
4
- handleChange?: (propertyId: string, value: (File | SavedDocumentReference)[] | undefined) => void;
4
+ handleChange?: (propertyId: string, value: (File | DocumentReference)[] | undefined) => void;
5
5
  onAutosave?: (fieldId: string) => void | Promise<void>;
6
6
  id: string;
7
+ fieldType?: 'document' | 'file';
7
8
  canUpdateProperty: boolean;
8
- value: (File | SavedDocumentReference)[] | undefined;
9
+ value: (File | DocumentReference)[] | undefined;
9
10
  setSnackbarError: (type: 'error' | 'success', message: string) => void;
10
11
  };
11
12
  export declare const DocumentList: (props: DocumentListProps) => React.JSX.Element;