@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.
@@ -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
  }
@@ -108,3 +108,7 @@ export type SectionsProps = {
108
108
  export type RichTextFormEntry = FormEntry & {
109
109
  uniqueId: string;
110
110
  };
111
+ export type InstanceLink = {
112
+ id: string;
113
+ objectId: string;
114
+ };
@@ -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>) => Promise<SavedDocumentReference[]>;
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 && instanceId && apiServices && objectId) {
596
+ if (fileInArray && apiServices && objectId) {
576
597
  // Determine property type from the entry
577
598
  const propertyType = entry?.input?.type;
578
599
  try {
579
- // Use uploadFiles for 'file' type, uploadDocuments for 'document' type
580
- const uploadedDocuments = propertyType === 'file'
581
- ? await uploadFiles(value, apiServices, '_create', {
600
+ let uploadedDocuments = [];
601
+ if (propertyType === 'file') {
602
+ uploadedDocuments = await uploadFiles(value, apiServices, '_create', {
582
603
  ...entry?.documentMetadata,
583
- })
584
- : await uploadDocuments(value, {
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.getByRole('textbox', { name: 'Name *' });
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.12.0-dev.4",
3
+ "version": "1.13.0-dev.0",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",