@evoke-platform/ui-components 1.12.0-dev.4 → 1.13.0-dev.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.
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +20 -2
- package/dist/published/components/custom/FormV2/components/types.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +7 -2
- package/dist/published/components/custom/FormV2/components/utils.js +33 -7
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { Box } from '../../layout';
|
|
|
7
7
|
import ErrorComponent from '../ErrorComponent';
|
|
8
8
|
import { evalDefaultVals, processValueUpdate } from './components/DefaultValues';
|
|
9
9
|
import Header from './components/Header';
|
|
10
|
-
import { convertPropertiesToParams, deleteDocuments, encodePageSlug, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault, plainTextToRtf, } from './components/utils';
|
|
10
|
+
import { convertPropertiesToParams, createFileLinks, deleteDocuments, encodePageSlug, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault, plainTextToRtf, } from './components/utils';
|
|
11
11
|
import FormRenderer from './FormRenderer';
|
|
12
12
|
function FormRendererContainer(props) {
|
|
13
13
|
const { instanceId, pageNavigation, dataType, display, formId, objectId, actionId, richTextEditor, onSubmit, onDiscardChanges: onDiscardChangesOverride, associatedObject, renderContainer, onSubmitError, sx, renderHeader, renderBody, renderFooter, } = props;
|
|
@@ -167,9 +167,25 @@ function FormRendererContainer(props) {
|
|
|
167
167
|
}
|
|
168
168
|
setInstance(updatedInstance);
|
|
169
169
|
};
|
|
170
|
+
const linkFiles = async (submission, linkTo) => {
|
|
171
|
+
// Create file links for any uploaded files after instance creation
|
|
172
|
+
for (const property of sanitizedObject?.properties?.filter((property) => property.type === 'file') ?? []) {
|
|
173
|
+
const files = submission[property.id];
|
|
174
|
+
if (files?.length) {
|
|
175
|
+
try {
|
|
176
|
+
await createFileLinks(files, linkTo, apiServices);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.error('Failed to create file links:', error);
|
|
180
|
+
// Don't fail the entire submission if file linking fails
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
170
185
|
const saveHandler = async (submission) => {
|
|
171
|
-
if (!form)
|
|
186
|
+
if (!form) {
|
|
172
187
|
return;
|
|
188
|
+
}
|
|
173
189
|
submission = await formatSubmission(submission, apiServices, objectId, instanceId, form, setSnackbarError);
|
|
174
190
|
try {
|
|
175
191
|
if (action?.type === 'create') {
|
|
@@ -180,6 +196,8 @@ function FormRendererContainer(props) {
|
|
|
180
196
|
.map((property) => property.id) ?? []),
|
|
181
197
|
});
|
|
182
198
|
if (response) {
|
|
199
|
+
// Manually link files to created instance.
|
|
200
|
+
await linkFiles(submission, { id: response.id, objectId: form.objectId });
|
|
183
201
|
onSubmissionSuccess(response);
|
|
184
202
|
}
|
|
185
203
|
}
|
|
@@ -4,7 +4,7 @@ import { LocalDateTime } from '@js-joda/core';
|
|
|
4
4
|
import { FieldErrors, FieldValues } from 'react-hook-form';
|
|
5
5
|
import { ObjectProperty } from '../../../../types';
|
|
6
6
|
import { AutocompleteOption } from '../../../core';
|
|
7
|
-
import { SavedDocumentReference } from './types';
|
|
7
|
+
import { InstanceLink, SavedDocumentReference } from './types';
|
|
8
8
|
export declare const scrollIntoViewWithOffset: (el: HTMLElement, offset: number, container?: HTMLElement) => void;
|
|
9
9
|
export declare const normalizeDateTime: (dateTime: LocalDateTime) => string;
|
|
10
10
|
export declare function isAddressProperty(key: string): boolean;
|
|
@@ -52,7 +52,12 @@ export declare const docProperties: Property[];
|
|
|
52
52
|
/**
|
|
53
53
|
* Upload files using the POST /files endpoint for sys__file objects
|
|
54
54
|
*/
|
|
55
|
-
export declare const uploadFiles: (files: (File | SavedDocumentReference)[], apiServices: ApiServices, actionId?: string, metadata?: Record<string, string
|
|
55
|
+
export declare const uploadFiles: (files: (File | SavedDocumentReference)[], apiServices: ApiServices, actionId?: string, metadata?: Record<string, string>, linkTo?: InstanceLink) => Promise<SavedDocumentReference[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Creates file links for uploaded files by calling the objects endpoint with sys__fileLink
|
|
58
|
+
* This is used after instance creation when the instance ID becomes available
|
|
59
|
+
*/
|
|
60
|
+
export declare const createFileLinks: (files: SavedDocumentReference[], linkedInstance: InstanceLink, apiServices: ApiServices) => Promise<void>;
|
|
56
61
|
export declare const uploadDocuments: (files: (File | SavedDocumentReference)[], metadata: Record<string, string>, apiServices: ApiServices, instanceId: string, objectId: string) => Promise<SavedDocumentReference[]>;
|
|
57
62
|
export declare const deleteDocuments: (submittedFields: FieldValues, requestSuccess: boolean, apiServices: ApiServices, object: Obj, instance: FieldValues, action?: Action, setSnackbarError?: React.Dispatch<React.SetStateAction<{
|
|
58
63
|
showAlert: boolean;
|
|
@@ -471,7 +471,7 @@ export const docProperties = [
|
|
|
471
471
|
/**
|
|
472
472
|
* Upload files using the POST /files endpoint for sys__file objects
|
|
473
473
|
*/
|
|
474
|
-
export const uploadFiles = async (files, apiServices, actionId = '_create', metadata) => {
|
|
474
|
+
export const uploadFiles = async (files, apiServices, actionId = '_create', metadata, linkTo) => {
|
|
475
475
|
// Separate already uploaded files from files that need uploading
|
|
476
476
|
const alreadyUploaded = files.filter((file) => !('size' in file));
|
|
477
477
|
const filesToUpload = files.filter((file) => 'size' in file);
|
|
@@ -486,6 +486,9 @@ export const uploadFiles = async (files, apiServices, actionId = '_create', meta
|
|
|
486
486
|
formData.append(key, value);
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
|
+
if (linkTo) {
|
|
490
|
+
formData.append('linkTo', JSON.stringify(linkTo));
|
|
491
|
+
}
|
|
489
492
|
const fileInstance = await apiServices.post(getPrefixedUrl(`/files`), formData);
|
|
490
493
|
return {
|
|
491
494
|
id: fileInstance.id,
|
|
@@ -495,6 +498,24 @@ export const uploadFiles = async (files, apiServices, actionId = '_create', meta
|
|
|
495
498
|
const uploadedFiles = await Promise.all(uploadPromises);
|
|
496
499
|
return [...alreadyUploaded, ...uploadedFiles];
|
|
497
500
|
};
|
|
501
|
+
/**
|
|
502
|
+
* Creates file links for uploaded files by calling the objects endpoint with sys__fileLink
|
|
503
|
+
* This is used after instance creation when the instance ID becomes available
|
|
504
|
+
*/
|
|
505
|
+
export const createFileLinks = async (files, linkedInstance, apiServices) => {
|
|
506
|
+
const linkPromises = files.map(async (file) => {
|
|
507
|
+
await apiServices.post(getPrefixedUrl(`/objects/sys__fileLink/instances`), {
|
|
508
|
+
name: 'File Link',
|
|
509
|
+
file: { id: file.id, name: file.name },
|
|
510
|
+
linkedInstance,
|
|
511
|
+
}, {
|
|
512
|
+
params: {
|
|
513
|
+
actionId: '_create',
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
await Promise.all(linkPromises);
|
|
518
|
+
};
|
|
498
519
|
export const uploadDocuments = async (files, metadata, apiServices, instanceId, objectId) => {
|
|
499
520
|
const allDocuments = [];
|
|
500
521
|
const formData = new FormData();
|
|
@@ -572,20 +593,25 @@ export async function formatSubmission(submission, apiServices, objectId, instan
|
|
|
572
593
|
if (isArray(value)) {
|
|
573
594
|
// Only upload if array contains File instances (not SavedDocumentReference)
|
|
574
595
|
const fileInArray = value.some((item) => item instanceof File);
|
|
575
|
-
if (fileInArray &&
|
|
596
|
+
if (fileInArray && apiServices && objectId) {
|
|
576
597
|
// Determine property type from the entry
|
|
577
598
|
const propertyType = entry?.input?.type;
|
|
578
599
|
try {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
600
|
+
let uploadedDocuments = [];
|
|
601
|
+
if (propertyType === 'file') {
|
|
602
|
+
uploadedDocuments = await uploadFiles(value, apiServices, '_create', {
|
|
582
603
|
...entry?.documentMetadata,
|
|
583
|
-
}
|
|
584
|
-
|
|
604
|
+
},
|
|
605
|
+
// Only pass linkTo if instanceId exists (update action)
|
|
606
|
+
instanceId ? { id: instanceId, objectId } : undefined);
|
|
607
|
+
}
|
|
608
|
+
else if (propertyType === 'document' && instanceId) {
|
|
609
|
+
uploadedDocuments = await uploadDocuments(value, {
|
|
585
610
|
type: '',
|
|
586
611
|
view_permission: '',
|
|
587
612
|
...entry?.documentMetadata,
|
|
588
613
|
}, apiServices, instanceId, objectId);
|
|
614
|
+
}
|
|
589
615
|
submission[key] = uploadedDocuments;
|
|
590
616
|
}
|
|
591
617
|
catch (err) {
|
|
@@ -1465,7 +1465,7 @@ describe('FormRenderer', () => {
|
|
|
1465
1465
|
const addButton = await screen.findByRole('button', { name: /add/i });
|
|
1466
1466
|
await user.click(addButton);
|
|
1467
1467
|
await screen.findByRole('dialog');
|
|
1468
|
-
const nameField = screen.
|
|
1468
|
+
const nameField = await screen.findByRole('textbox', { name: 'Name *' });
|
|
1469
1469
|
await user.type(nameField, 'New Collection Item');
|
|
1470
1470
|
const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
|
|
1471
1471
|
await user.click(submitButton);
|