From f873a5c47c86a045578794cdf3b860e56097f0fd Mon Sep 17 00:00:00 2001 From: Jose Garcia Date: Thu, 28 Aug 2025 16:20:05 +0200 Subject: [PATCH 01/11] ABC - Duplicate record --- .../record-modal/graphql/queries.ts | 2 ++ .../record-modal/record-modal.component.html | 9 +++++++ .../record-modal/record-modal.component.ts | 24 +++++++++++++++++++ .../ui/core-grid/core-grid.component.ts | 4 +++- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/libs/shared/src/lib/components/record-modal/graphql/queries.ts b/libs/shared/src/lib/components/record-modal/graphql/queries.ts index 493436956d..07e1c4d6a2 100644 --- a/libs/shared/src/lib/components/record-modal/graphql/queries.ts +++ b/libs/shared/src/lib/components/record-modal/graphql/queries.ts @@ -19,6 +19,7 @@ export const GET_RECORD_BY_ID = gql` form { id structure + canCreateRecords permissions { recordsUnicity } @@ -42,6 +43,7 @@ export const GET_FORM_STRUCTURE = gql` form(id: $id) { id structure + canCreateRecords metadata { name automated diff --git a/libs/shared/src/lib/components/record-modal/record-modal.component.html b/libs/shared/src/lib/components/record-modal/record-modal.component.html index ff0ae52be2..e738add7e0 100644 --- a/libs/shared/src/lib/components/record-modal/record-modal.component.html +++ b/libs/shared/src/lib/components/record-modal/record-modal.component.html @@ -12,6 +12,15 @@ [survey]="survey" [surveyNext]="surveyNext || undefined" > + + + Duplicate record + { + if (!this.form?.id) return; + const { FormModalComponent } = await import('../form-modal/form-modal.component'); + const dialogRef = this.dialog.open(FormModalComponent, { + disableClose: true, + data: { + template: this.form.id, + prefillData: this.record?.data, + askForConfirm: false, + }, + autoFocus: false, + }); + dialogRef.closed + .pipe(takeUntil(this.destroy$)) + .subscribe((value: any) => { + if (value) { + this.dialogRef.close(value); + } + }); + } + /** * Opens the history of the record in a modal. */ diff --git a/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts b/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts index b106754307..44f409c979 100644 --- a/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts +++ b/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts @@ -1163,7 +1163,9 @@ export class CoreGridComponent dialogRef.closed .pipe(takeUntil(this.destroy$)) .subscribe((value: any) => { - if (value) { + if (value?.data) { + this.reloadData(); + } else if (value) { this.onUpdate(isArray ? items : [items]); } }); From a17c70aa74b5fd708a13386f85b1f107dd2fb4b6 Mon Sep 17 00:00:00 2001 From: Jose Garcia Date: Thu, 28 Aug 2025 16:41:47 +0200 Subject: [PATCH 02/11] ABC - Duplicate record --- .../record-modal/record-modal.component.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/shared/src/lib/components/record-modal/record-modal.component.ts b/libs/shared/src/lib/components/record-modal/record-modal.component.ts index e290b30a97..df9ca60ec5 100644 --- a/libs/shared/src/lib/components/record-modal/record-modal.component.ts +++ b/libs/shared/src/lib/components/record-modal/record-modal.component.ts @@ -268,7 +268,9 @@ export class RecordModalComponent */ public async onDuplicate(): Promise { if (!this.form?.id) return; - const { FormModalComponent } = await import('../form-modal/form-modal.component'); + const { FormModalComponent } = await import( + '../form-modal/form-modal.component' + ); const dialogRef = this.dialog.open(FormModalComponent, { disableClose: true, data: { @@ -278,13 +280,11 @@ export class RecordModalComponent }, autoFocus: false, }); - dialogRef.closed - .pipe(takeUntil(this.destroy$)) - .subscribe((value: any) => { - if (value) { - this.dialogRef.close(value); - } - }); + dialogRef.closed.pipe(takeUntil(this.destroy$)).subscribe((value: any) => { + if (value) { + this.dialogRef.close(value); + } + }); } /** From 56fb3676a56a389b0e2efa22a46d7133dbc20f2f Mon Sep 17 00:00:00 2001 From: Jose Garcia Date: Thu, 28 Aug 2025 17:15:51 +0200 Subject: [PATCH 03/11] quick fixes --- libs/shared/src/i18n/en.json | 1 + libs/shared/src/i18n/fr.json | 1 + libs/shared/src/i18n/test.json | 1 + .../lib/components/record-modal/record-modal.component.html | 2 +- .../lib/components/record-modal/record-modal.component.ts | 5 ----- .../src/lib/components/ui/core-grid/core-grid.component.ts | 4 +--- 6 files changed, 5 insertions(+), 9 deletions(-) diff --git a/libs/shared/src/i18n/en.json b/libs/shared/src/i18n/en.json index 29ef198ebe..3a6adb39c1 100644 --- a/libs/shared/src/i18n/en.json +++ b/libs/shared/src/i18n/en.json @@ -181,6 +181,7 @@ "downloadObject": "Download {{name}}", "downloadTemplate": "Get template", "duplicate": "Duplicate", + "duplicateRecord": "Duplicate record", "edit": "Edit", "email": { "customTemplate": "Custom template", diff --git a/libs/shared/src/i18n/fr.json b/libs/shared/src/i18n/fr.json index 336324b410..f8049eec70 100644 --- a/libs/shared/src/i18n/fr.json +++ b/libs/shared/src/i18n/fr.json @@ -181,6 +181,7 @@ "downloadObject": "Télécharger {{name}}", "downloadTemplate": "Télécharger le modèle", "duplicate": "Dupliquer", + "duplicateRecord": "Dupliquer l'enregistrement", "edit": "Modifier", "email": { "customTemplate": "Modèle personnalisé", diff --git a/libs/shared/src/i18n/test.json b/libs/shared/src/i18n/test.json index 0d706b017c..aa9f2b232a 100644 --- a/libs/shared/src/i18n/test.json +++ b/libs/shared/src/i18n/test.json @@ -181,6 +181,7 @@ "downloadObject": "****** {{name}}", "downloadTemplate": "******", "duplicate": "******", + "duplicateRecord": "******", "edit": "******", "email": { "customTemplate": "******", diff --git a/libs/shared/src/lib/components/record-modal/record-modal.component.html b/libs/shared/src/lib/components/record-modal/record-modal.component.html index e738add7e0..ac9d58119c 100644 --- a/libs/shared/src/lib/components/record-modal/record-modal.component.html +++ b/libs/shared/src/lib/components/record-modal/record-modal.component.html @@ -19,7 +19,7 @@ category="secondary" (click)="onDuplicate()" > - Duplicate record + {{ 'common.duplicateRecord' | translate }} { - if (value) { - this.dialogRef.close(value); - } - }); } /** diff --git a/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts b/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts index 44f409c979..b106754307 100644 --- a/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts +++ b/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts @@ -1163,9 +1163,7 @@ export class CoreGridComponent dialogRef.closed .pipe(takeUntil(this.destroy$)) .subscribe((value: any) => { - if (value?.data) { - this.reloadData(); - } else if (value) { + if (value) { this.onUpdate(isArray ? items : [items]); } }); From 77c4f15fc6240a97416fae19ed5af956006db121 Mon Sep 17 00:00:00 2001 From: Jose Garcia Date: Thu, 28 Aug 2025 17:17:03 +0200 Subject: [PATCH 04/11] quick fixes --- .../src/lib/components/record-modal/record-modal.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/shared/src/lib/components/record-modal/record-modal.component.ts b/libs/shared/src/lib/components/record-modal/record-modal.component.ts index cf2079f59a..afaf7456cd 100644 --- a/libs/shared/src/lib/components/record-modal/record-modal.component.ts +++ b/libs/shared/src/lib/components/record-modal/record-modal.component.ts @@ -271,7 +271,7 @@ export class RecordModalComponent const { FormModalComponent } = await import( '../form-modal/form-modal.component' ); - const dialogRef = this.dialog.open(FormModalComponent, { + this.dialog.open(FormModalComponent, { disableClose: true, data: { template: this.form.id, From c9f063d61c6ab76e2143dd8df35c200d4edb202a Mon Sep 17 00:00:00 2001 From: Jose Garcia Date: Mon, 6 Oct 2025 15:24:46 +0200 Subject: [PATCH 05/11] Clone record fixes and improvements --- .../edit-action-button-modal.component.html | 80 ++++++++++ .../edit-action-button-modal.component.ts | 147 +++++++++++++++++- libs/shared/src/i18n/en.json | 1 + libs/shared/src/i18n/fr.json | 1 + libs/shared/src/i18n/test.json | 1 + .../action-button/action-button.component.ts | 70 ++++++++- .../action-button/action-button.type.ts | 15 ++ .../ui/core-grid/core-grid.component.ts | 2 +- 8 files changed, 312 insertions(+), 5 deletions(-) diff --git a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html index 2d0ed3b5c1..26f9a7539a 100644 --- a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html +++ b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html @@ -270,6 +270,86 @@

{{ 'common.preview' | translate }}

" > + +
+
+ + + {{ 'models.dashboard.actionButtons.actions.afterCloneNavigateTo' | translate }} + + +
+ +
+ + + {{ 'components.widget.settings.grid.actions.goTo.target.label' | translate }} + + + +
+ + + + {{ page.name }} + + +
+
+ {{ 'components.widget.settings.grid.actions.goTo.field.label' | translate }} + + + + {{ field.text || field.name }} + + + + {{ field.text || field.name }} + + {{ + field.name === '$attribute' + ? subField.text || subField.name + : field.name + ' - ' + (subField.text || subField.name) + }} + + + + +
+
+
+ +
+ + + {{ 'models.dashboard.actionButtons.actions.url' | translate }} + + + +
+ + +
+ + + {{ 'models.dashboard.actionButtons.actions.openInNewTab' | translate }} + + +
+
+
+
+
diff --git a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts index e701ef211d..d2fc1e24a8 100644 --- a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts +++ b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts @@ -24,6 +24,9 @@ import { EmailService, FieldMapperComponent, Form, + Application, + ContentType, + Page, INLINE_EDITOR_CONFIG, QueryBuilderModule, QueryBuilderService, @@ -124,6 +127,10 @@ export class EditActionButtonModalComponent public sendNotificationTemplates: Form[] = []; /** Fields, of current page context resource, if any */ public sendNotificationFields: any[] = []; + /** All fields of current context resource for targetPage field selection */ + public availableFields: any[] = []; + /** Available pages from the application for targetPage selection */ + public pages: any[] = []; /** * Component for editing a dashboard action button @@ -169,6 +176,10 @@ export class EditActionButtonModalComponent this.dataTemplateService.getAutoCompleterPageKeys() ); if (!isNil(this.data.dashboard)) { + // Build list of pages for targetPage selection + this.pages = this.getPages( + this.applicationService.application.getValue() + ); // Listen to form changes this.setFormListeners(); // Get context resource data, if any @@ -182,6 +193,8 @@ export class EditActionButtonModalComponent }) .subscribe({ next: ({ data }) => { + const queryTemp: any = data.resource; + const newData = this.queryBuilder.getFields(queryTemp.queryName); this.editRecordTemplates = data.resource.forms ?? []; this.sendNotificationFields = this.queryBuilder.getFields( data.resource?.queryName as string @@ -189,6 +202,7 @@ export class EditActionButtonModalComponent this.resourceFields = data.resource.fields.filter((f: any) => ['resource', 'resources'].includes(f.type) ); + this.availableFields = newData; }, }); } @@ -303,6 +317,66 @@ export class EditActionButtonModalComponent enabled: [!!get(data, 'cloneRecord', false)], template: [get(data, 'cloneRecord.template', '')], autoReload: [get(data, 'cloneRecord.autoReload', false)], + onSave: this.fb.group({ + navigateTo: this.fb.group( + { + enabled: [ + !!get( + data, + 'cloneRecord.onSave.navigateTo.enabled', + false + ), + ], + targetUrl: this.fb.group({ + enabled: [ + !!get( + data, + 'cloneRecord.onSave.navigateTo.targetUrl.enabled', + false + ), + ], + href: [ + get( + data, + 'cloneRecord.onSave.navigateTo.targetUrl.href', + '' + ), + ], + openInNewTab: [ + get( + data, + 'cloneRecord.onSave.navigateTo.targetUrl.openInNewTab', + true + ), + ], + }), + targetPage: this.fb.group({ + enabled: [ + !!get( + data, + 'cloneRecord.onSave.navigateTo.targetPage.enabled', + false + ), + ], + pageUrl: [ + get( + data, + 'cloneRecord.onSave.navigateTo.targetPage.pageUrl', + '' + ), + ], + field: [ + get( + data, + 'cloneRecord.onSave.navigateTo.targetPage.field', + '' + ), + ], + }), + }, + { validator: this.cloneRecordNavigateToValidator } + ), + }), }), addRecord: this.fb.group( { @@ -437,6 +511,7 @@ export class EditActionButtonModalComponent const navigateToControls = [ form.get('action.navigateTo.previousPage'), form.get('action.navigateTo.targetUrl.enabled'), + form.get('action.cloneRecord.onSave.navigateTo.targetUrl.enabled'), ]; // Apply the utility function to both sets of controls @@ -464,6 +539,7 @@ export class EditActionButtonModalComponent this.selectedResource = data.resource; this.addRecordTemplates = data.resource.forms ?? []; this.addRecordFields = data.resource.fields ?? []; + this.availableFields = data.resource.fields ?? []; }, }); } @@ -620,6 +696,17 @@ export class EditActionButtonModalComponent /** On click on the preview button open the href */ public preview(): void { let href = this.form.get('action.navigateTo.targetUrl.href')?.value; + let isNewTab = + this.form.get('action.navigateTo.targetUrl.openInNewTab')?.value ?? true; + if (!href) { + href = this.form.get( + 'action.cloneRecord.onSave.navigateTo.targetUrl.href' + )?.value; + isNewTab = + this.form.get( + 'action.cloneRecord.onSave.navigateTo.targetUrl.openInNewTab' + )?.value ?? true; + } if (href) { //regex to verify if it's a page id key const regex = /{{page\((.*?)\)}}/; @@ -627,7 +714,6 @@ export class EditActionButtonModalComponent if (match) { href = this.dataTemplateService.getButtonLink(match[1]); } - const isNewTab = this.form.get('openInNewTab')?.value ?? true; if (isNewTab) window.open(href, '_blank'); else this.router.navigate([href]); } @@ -664,6 +750,7 @@ export class EditActionButtonModalComponent cloneRecord: { template: this.form.get('action.cloneRecord.template')?.value, autoReload: this.form.get('action.cloneRecord.autoReload')?.value, + onSave: this.form.get('action.cloneRecord.onSave')?.value, }, }), // If addRecord enabled @@ -782,4 +869,62 @@ export class EditActionButtonModalComponent } return null; }; + + /** + * Get available pages from app + * + * @param application application + * @returns list of pages and their url + */ + private getPages(application: Application | null) { + return ( + application?.pages?.map((page: any) => ({ + id: page.id, + name: page.name, + urlParams: this.getPageUrlParams(application, page), + placeholder: `{{page(${page.id})}}`, + })) || [] + ); + } + + /** + * Get page url params + * + * @param application application + * @param page page to get url from + * @returns url of the page + */ + private getPageUrlParams(application: Application, page: Page): string { + const applicationPath = + this.applicationService.getApplicationPath(application); + return page.type === ContentType.form + ? `${applicationPath}/${page.type}/${page.id}` + : `${applicationPath}/${page.type}/${page.content}`; + } + + /** + * Validator to ensure that at least one clone record navigateTo action is enabled + * + * @param control form group + * @returns validation errors + */ + cloneRecordNavigateToValidator: ValidatorFn = ( + control: AbstractControl + ): ValidationErrors | null => { + const navigateTo = control.value; + if (navigateTo?.enabled) { + const atLeastOneEnabled = + navigateTo.targetPage?.enabled || navigateTo.targetUrl?.enabled; + const hrefValid = + !navigateTo.targetUrl?.enabled || + (navigateTo.targetUrl.enabled && navigateTo.targetUrl.href); + const pageUrlValid = + !navigateTo.targetPage?.enabled || + (navigateTo.targetPage.enabled && navigateTo.targetPage.pageUrl); + if (!atLeastOneEnabled) return { atLeastOneRequired: true }; + if (!hrefValid) return { hrefRequired: true }; + if (!pageUrlValid) return { pageUrlRequired: true }; + } + return null; + }; } diff --git a/libs/shared/src/i18n/en.json b/libs/shared/src/i18n/en.json index aade9e70fb..4d2d7ac344 100644 --- a/libs/shared/src/i18n/en.json +++ b/libs/shared/src/i18n/en.json @@ -2700,6 +2700,7 @@ "template": "Select a resource template for addition of new records" } }, + "afterCloneNavigateTo": "After clone, navigate to", "autoReload": "Auto reload dashboard on save", "cloneRecord": { "text": "Clone record", diff --git a/libs/shared/src/i18n/fr.json b/libs/shared/src/i18n/fr.json index 736ed057f1..208fbe93ed 100644 --- a/libs/shared/src/i18n/fr.json +++ b/libs/shared/src/i18n/fr.json @@ -2703,6 +2703,7 @@ "template": "Sélectionner un modèle pour l'ajout de nouveaux enregistrements" } }, + "afterCloneNavigateTo": "Après la duplication, naviguer vers", "autoReload": "Recharger le tableau de bord après la sauvegarde", "cloneRecord": { "text": "Dupliquer l'enregistrement actuel", diff --git a/libs/shared/src/i18n/test.json b/libs/shared/src/i18n/test.json index 2741f2a494..4be04c2580 100644 --- a/libs/shared/src/i18n/test.json +++ b/libs/shared/src/i18n/test.json @@ -2700,6 +2700,7 @@ "template": "******" } }, + "afterCloneNavigateTo": "******", "autoReload": "******", "cloneRecord": { "text": "******", diff --git a/libs/shared/src/lib/components/action-button/action-button.component.ts b/libs/shared/src/lib/components/action-button/action-button.component.ts index a8cadccb3d..f692bf921d 100644 --- a/libs/shared/src/lib/components/action-button/action-button.component.ts +++ b/libs/shared/src/lib/components/action-button/action-button.component.ts @@ -1,4 +1,11 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + OnInit, + Output, + Inject, +} from '@angular/core'; import { CommonModule, Location } from '@angular/common'; import { ActionButton } from './action-button.type'; import { ButtonModule, TooltipModule } from '@oort-front/ui'; @@ -55,12 +62,17 @@ export class ActionButtonComponent public contextId!: string; /** Email notification, for subscribe & unsubscribe actions */ private emailNotification?: EmailNotification; + /** Current environment */ + private environment: any; /** @returns Should hide button */ get showButton(): boolean { if (this.actionButton.editRecord && !this.contextId) { return false; } + if (this.actionButton.cloneRecord && !this.contextId) { + return false; + } if (this.actionButton.subscribeToNotification) { if (this.emailNotification && !this.emailNotification.userSubscribed) { return true; @@ -81,6 +93,7 @@ export class ActionButtonComponent /** * Dashboard action button component. * + * @param environment Current environment * @param dialog Dialog service * @param dataTemplateService DataTemplate service * @param router Angular router @@ -96,6 +109,7 @@ export class ActionButtonComponent * @param contextService Shared context service */ constructor( + @Inject('environment') environment: any, public dialog: Dialog, private dataTemplateService: DataTemplateService, private router: Router, @@ -111,6 +125,7 @@ export class ActionButtonComponent private contextService: ContextService ) { super(); + this.environment = environment; this.activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe({ next: ({ id }) => { this.contextId = id; @@ -342,6 +357,7 @@ export class ActionButtonComponent disableClose: true, data: { ...(this.actionButton.editRecord && { recordId: this.contextId }), // button must be hidden in html if editRecord is enabled & no contextId + ...(this.actionButton.cloneRecord && { recordId: this.contextId }), // button must be hidden in html if cloneRecord is enabled & no contextId ...(template && { template }), actionButtonCtx: true, prefillData, @@ -419,8 +435,44 @@ export class ActionButtonComponent callback(); } } else { - // Edit record action - callback(); + // Edit or Clone record action + const isClone = !!this.actionButton.cloneRecord; + if ( + isClone && + this.actionButton.cloneRecord?.onSave?.navigateTo?.enabled + ) { + const nav = this.actionButton.cloneRecord.onSave.navigateTo; + if (nav.targetPage?.enabled && nav.targetPage.pageUrl) { + let fullUrl = this.getPageUrl( + nav.targetPage.pageUrl as string + ); + if (nav.targetPage.field && value?.data) { + const fieldPath = nav.targetPage.field; + const paramValue = get(value.data, fieldPath); + fullUrl = `${fullUrl}?${fieldPath}=${paramValue}`; + } + if (fullUrl.startsWith('./')) + fullUrl = fullUrl.substring(1); + this.router.navigateByUrl(fullUrl); + } else if (nav.targetUrl?.enabled && nav.targetUrl.href) { + const href = this.contextService.replaceContext( + this.dataTemplateService.renderLink(nav.targetUrl.href) + ); + if (nav.targetUrl.openInNewTab) { + window.open(href, '_blank'); + } else { + if (href?.startsWith('./')) { + this.router.navigateByUrl(href.substring(1)); + } else { + window.location.href = href; + } + } + } else { + callback(); + } + } else { + callback(); + } } } }); @@ -556,4 +608,16 @@ export class ActionButtonComponent }, }); } + + /** + * Get page url full link taking into account the environment. + * + * @param pageUrlParams page url params + * @returns url of the page + */ + private getPageUrl(pageUrlParams: string): string { + return this.environment.module === 'backoffice' + ? `applications/${pageUrlParams}` + : `${pageUrlParams}`; + } } diff --git a/libs/shared/src/lib/components/action-button/action-button.type.ts b/libs/shared/src/lib/components/action-button/action-button.type.ts index b5a0ed189b..8173014757 100644 --- a/libs/shared/src/lib/components/action-button/action-button.type.ts +++ b/libs/shared/src/lib/components/action-button/action-button.type.ts @@ -38,6 +38,21 @@ export type ActionButton = { cloneRecord?: { template?: string; autoReload?: boolean; + onSave?: { + navigateTo?: { + enabled?: boolean; + targetUrl?: { + enabled?: boolean; + href?: string; + openInNewTab?: boolean; + }; + targetPage?: { + enabled?: boolean; + pageUrl?: string; + field?: string; + }; + }; + }; }; // Add Record addRecord?: { diff --git a/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts b/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts index c5e8dcd016..7eef8ec1c9 100644 --- a/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts +++ b/libs/shared/src/lib/components/ui/core-grid/core-grid.component.ts @@ -979,7 +979,7 @@ export class CoreGridComponent if (event.field) { const field = get(event, 'field', ''); const value = get(event, `item.${field}`); - fullUrl = `${fullUrl}?id=${value}`; + fullUrl = `${fullUrl}?${field}=${value}`; } this.router.navigateByUrl(fullUrl); } From c8034285f03cd9317b85c544733626cd5d94af1c Mon Sep 17 00:00:00 2001 From: Antoine Hurard Date: Fri, 10 Oct 2025 10:12:25 +0200 Subject: [PATCH 06/11] improve layout of edit-action-buttom modal --- .../edit-action-button-modal.component.html | 129 ++++++++++++++---- libs/shared/src/i18n/en.json | 5 +- libs/shared/src/i18n/fr.json | 5 +- libs/shared/src/i18n/test.json | 5 +- 4 files changed, 111 insertions(+), 33 deletions(-) diff --git a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html index 26f9a7539a..5b60cf0fdb 100644 --- a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html +++ b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html @@ -140,7 +140,7 @@

{{ 'common.preview' | translate }}

@@ -245,11 +245,15 @@

{{ 'common.preview' | translate }}

-
+
- @@ -275,51 +279,101 @@

{{ 'common.preview' | translate }}

- {{ 'models.dashboard.actionButtons.actions.afterCloneNavigateTo' | translate }} + {{ + 'models.dashboard.actionButtons.actions.cloneRecord.onSave.navigateTo' + | translate + }} -
+
-
+
- {{ 'components.widget.settings.grid.actions.goTo.target.label' | translate }} + {{ + 'components.widget.settings.grid.actions.goTo.target.label' + | translate + }} - -
+ +
- - + + {{ page.name }}
-
- {{ 'components.widget.settings.grid.actions.goTo.field.label' | translate }} +
+ {{ + 'components.widget.settings.grid.actions.goTo.field.label' + | translate + }} - - + + {{ field.text || field.name }} - + {{ field.text || field.name }} {{ field.name === '$attribute' ? subField.text || subField.name - : field.name + ' - ' + (subField.text || subField.name) + : field.name + + ' - ' + + (subField.text || subField.name) }} @@ -329,20 +383,41 @@

{{ 'common.preview' | translate }}

-
+
- {{ 'models.dashboard.actionButtons.actions.url' | translate }} + {{ + 'models.dashboard.actionButtons.actions.url' + | translate + }} - -
- - + +
+ +
- {{ 'models.dashboard.actionButtons.actions.openInNewTab' | translate }} + {{ + 'models.dashboard.actionButtons.actions.openInNewTab' + | translate + }}
diff --git a/libs/shared/src/i18n/en.json b/libs/shared/src/i18n/en.json index 4d2d7ac344..bb5faafb98 100644 --- a/libs/shared/src/i18n/en.json +++ b/libs/shared/src/i18n/en.json @@ -183,7 +183,6 @@ "downloadObject": "Download {{name}}", "downloadTemplate": "Get template", "duplicate": "Duplicate", - "duplicateRecord": "Duplicate record", "edit": "Edit", "email": { "customTemplate": "Custom template", @@ -2700,9 +2699,11 @@ "template": "Select a resource template for addition of new records" } }, - "afterCloneNavigateTo": "After clone, navigate to", "autoReload": "Auto reload dashboard on save", "cloneRecord": { + "onSave": { + "navigateTo": "After clone, navigate to" + }, "text": "Clone record", "tooltip": { "template": "Select a resource template to clone the current record.\n If no template is selected, latest form used by the record will be used" diff --git a/libs/shared/src/i18n/fr.json b/libs/shared/src/i18n/fr.json index 208fbe93ed..4b18f66d9f 100644 --- a/libs/shared/src/i18n/fr.json +++ b/libs/shared/src/i18n/fr.json @@ -183,7 +183,6 @@ "downloadObject": "Télécharger {{name}}", "downloadTemplate": "Télécharger le modèle", "duplicate": "Dupliquer", - "duplicateRecord": "Dupliquer l'enregistrement", "edit": "Modifier", "email": { "customTemplate": "Modèle personnalisé", @@ -2703,9 +2702,11 @@ "template": "Sélectionner un modèle pour l'ajout de nouveaux enregistrements" } }, - "afterCloneNavigateTo": "Après la duplication, naviguer vers", "autoReload": "Recharger le tableau de bord après la sauvegarde", "cloneRecord": { + "onSave": { + "navigateTo": "Après la duplication, naviguer vers" + }, "text": "Dupliquer l'enregistrement actuel", "tooltip": { "template": "Sélectionner un modèle pour la duplication de l'enregistrement actuel.\n Si aucun modèle n'est sélectionné, le dernier modèle utilisé par l'enregistrement actuel sera utilisé" diff --git a/libs/shared/src/i18n/test.json b/libs/shared/src/i18n/test.json index 4be04c2580..e339057261 100644 --- a/libs/shared/src/i18n/test.json +++ b/libs/shared/src/i18n/test.json @@ -183,7 +183,6 @@ "downloadObject": "****** {{name}}", "downloadTemplate": "******", "duplicate": "******", - "duplicateRecord": "******", "edit": "******", "email": { "customTemplate": "******", @@ -2700,9 +2699,11 @@ "template": "******" } }, - "afterCloneNavigateTo": "******", "autoReload": "******", "cloneRecord": { + "onSave": { + "navigateTo": "******" + }, "text": "******", "tooltip": { "template": "******" From f3e5599524ccd096ca7583fbcd7fea4610f401d8 Mon Sep 17 00:00:00 2001 From: Antoine Hurard Date: Fri, 10 Oct 2025 11:09:07 +0200 Subject: [PATCH 07/11] improve action button form --- .../edit-action-button-modal.component.ts | 70 ++++++++++++++----- .../action-button/action-button.component.ts | 41 ++++++----- .../action-button/action-button.type.ts | 3 - 3 files changed, 78 insertions(+), 36 deletions(-) diff --git a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts index d2fc1e24a8..7faf862dfc 100644 --- a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts +++ b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts @@ -320,18 +320,24 @@ export class EditActionButtonModalComponent onSave: this.fb.group({ navigateTo: this.fb.group( { + // Enabled if one of the two navigateTo options is set enabled: [ !!get( data, - 'cloneRecord.onSave.navigateTo.enabled', + 'cloneRecord.onSave.navigateTo.targetUrl.href', false - ), + ) || + !!get( + data, + 'cloneRecord.onSave.navigateTo.targetPage.pageUrl', + false + ), ], targetUrl: this.fb.group({ enabled: [ !!get( data, - 'cloneRecord.onSave.navigateTo.targetUrl.enabled', + 'cloneRecord.onSave.navigateTo.targetUrl.href', false ), ], @@ -354,7 +360,7 @@ export class EditActionButtonModalComponent enabled: [ !!get( data, - 'cloneRecord.onSave.navigateTo.targetPage.enabled', + 'cloneRecord.onSave.navigateTo.targetPage.pageUrl', false ), ], @@ -497,8 +503,9 @@ export class EditActionButtonModalComponent ), }); - // Setting up mutual exclusivity for action controls and navigateTo controls - const actionControls = [ + // Set up mutual exclusivity + // Between action controls + this.setupMutualExclusivity([ form.get('action.navigateTo.enabled'), form.get('action.editRecord.enabled'), form.get('action.cloneRecord.enabled'), @@ -506,18 +513,17 @@ export class EditActionButtonModalComponent form.get('action.subscribeToNotification.enabled'), form.get('action.unsubscribeFromNotification.enabled'), form.get('action.sendNotification.enabled'), - ]; - - const navigateToControls = [ + ] as AbstractControl[]); + // Between navigateTo controls + this.setupMutualExclusivity([ form.get('action.navigateTo.previousPage'), form.get('action.navigateTo.targetUrl.enabled'), + ] as AbstractControl[]); + // Between navigateTo controls of clone record action + this.setupMutualExclusivity([ + form.get('action.cloneRecord.onSave.navigateTo.targetPage.enabled'), form.get('action.cloneRecord.onSave.navigateTo.targetUrl.enabled'), - ]; - - // Apply the utility function to both sets of controls - this.setupMutualExclusivity(actionControls as AbstractControl[]); - this.setupMutualExclusivity(navigateToControls as AbstractControl[]); - + ] as AbstractControl[]); return form; }; @@ -750,7 +756,39 @@ export class EditActionButtonModalComponent cloneRecord: { template: this.form.get('action.cloneRecord.template')?.value, autoReload: this.form.get('action.cloneRecord.autoReload')?.value, - onSave: this.form.get('action.cloneRecord.onSave')?.value, + onSave: { + ...(this.form.get('action.cloneRecord.onSave.navigateTo.enabled') + ?.value && { + navigateTo: { + // If targetPage enabled + ...(this.form.get( + 'action.cloneRecord.onSave.navigateTo.targetPage.enabled' + )?.value && { + targetPage: { + pageUrl: this.form.get( + 'action.cloneRecord.onSave.navigateTo.targetPage.pageUrl' + )?.value, + field: this.form.get( + 'action.cloneRecord.onSave.navigateTo.targetPage.field' + )?.value, + }, + }), + // If targetUrl enabled + ...(this.form.get( + 'action.cloneRecord.onSave.navigateTo.targetUrl.enabled' + )?.value && { + targetUrl: { + href: this.form.get( + 'action.cloneRecord.onSave.navigateTo.targetUrl.href' + )?.value, + openInNewTab: this.form.get( + 'action.cloneRecord.onSave.navigateTo.targetUrl.openInNewTab' + )?.value, + }, + }), + }, + }), + }, }, }), // If addRecord enabled diff --git a/libs/shared/src/lib/components/action-button/action-button.component.ts b/libs/shared/src/lib/components/action-button/action-button.component.ts index f692bf921d..474f56cb8f 100644 --- a/libs/shared/src/lib/components/action-button/action-button.component.ts +++ b/libs/shared/src/lib/components/action-button/action-button.component.ts @@ -316,6 +316,7 @@ export class ActionButtonComponent // Prefill data for addRecord & cloneRecord const loadPrefillData$ = () => { if (this.actionButton.cloneRecord && this.contextId) { + console.log('prepare to clone'); return this.apollo .query({ query: GET_RECORD_BY_ID, @@ -356,8 +357,7 @@ export class ActionButtonComponent const dialogRef = this.dialog.open(FormModalComponent, { disableClose: true, data: { - ...(this.actionButton.editRecord && { recordId: this.contextId }), // button must be hidden in html if editRecord is enabled & no contextId - ...(this.actionButton.cloneRecord && { recordId: this.contextId }), // button must be hidden in html if cloneRecord is enabled & no contextId + ...(this.actionButton.editRecord && { recordId: this.contextId }), // Modal will open current record ...(template && { template }), actionButtonCtx: true, prefillData, @@ -436,29 +436,35 @@ export class ActionButtonComponent } } else { // Edit or Clone record action - const isClone = !!this.actionButton.cloneRecord; - if ( - isClone && - this.actionButton.cloneRecord?.onSave?.navigateTo?.enabled - ) { - const nav = this.actionButton.cloneRecord.onSave.navigateTo; - if (nav.targetPage?.enabled && nav.targetPage.pageUrl) { + if (this.actionButton.cloneRecord) { + // Clone action + const navigateTo = + this.actionButton.cloneRecord.onSave?.navigateTo; + if (navigateTo?.targetPage && navigateTo.targetPage.pageUrl) { + // Navigate to page in app builder let fullUrl = this.getPageUrl( - nav.targetPage.pageUrl as string + navigateTo.targetPage.pageUrl as string ); - if (nav.targetPage.field && value?.data) { - const fieldPath = nav.targetPage.field; + if (navigateTo.targetPage.field && value?.data) { + // Add query parameter + const fieldPath = navigateTo.targetPage.field; const paramValue = get(value.data, fieldPath); fullUrl = `${fullUrl}?${fieldPath}=${paramValue}`; } - if (fullUrl.startsWith('./')) - fullUrl = fullUrl.substring(1); + // if (fullUrl.startsWith('./')) + // fullUrl = fullUrl.substring(1); this.router.navigateByUrl(fullUrl); - } else if (nav.targetUrl?.enabled && nav.targetUrl.href) { + } else if ( + navigateTo?.targetUrl && + navigateTo.targetUrl.href + ) { + // Navigate to any other url const href = this.contextService.replaceContext( - this.dataTemplateService.renderLink(nav.targetUrl.href) + this.dataTemplateService.renderLink( + navigateTo.targetUrl.href + ) ); - if (nav.targetUrl.openInNewTab) { + if (navigateTo.targetUrl.openInNewTab) { window.open(href, '_blank'); } else { if (href?.startsWith('./')) { @@ -471,6 +477,7 @@ export class ActionButtonComponent callback(); } } else { + // Edit Action callback(); } } diff --git a/libs/shared/src/lib/components/action-button/action-button.type.ts b/libs/shared/src/lib/components/action-button/action-button.type.ts index 8173014757..92e0ab7b24 100644 --- a/libs/shared/src/lib/components/action-button/action-button.type.ts +++ b/libs/shared/src/lib/components/action-button/action-button.type.ts @@ -40,14 +40,11 @@ export type ActionButton = { autoReload?: boolean; onSave?: { navigateTo?: { - enabled?: boolean; targetUrl?: { - enabled?: boolean; href?: string; openInNewTab?: boolean; }; targetPage?: { - enabled?: boolean; pageUrl?: string; field?: string; }; From 84cc61b076de5708e4dc094e7729deaebbfd0024 Mon Sep 17 00:00:00 2001 From: Antoine Hurard Date: Fri, 10 Oct 2025 11:17:33 +0200 Subject: [PATCH 08/11] remove useless line --- .../src/lib/components/action-button/action-button.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/shared/src/lib/components/action-button/action-button.component.ts b/libs/shared/src/lib/components/action-button/action-button.component.ts index 474f56cb8f..2846da62c0 100644 --- a/libs/shared/src/lib/components/action-button/action-button.component.ts +++ b/libs/shared/src/lib/components/action-button/action-button.component.ts @@ -451,8 +451,6 @@ export class ActionButtonComponent const paramValue = get(value.data, fieldPath); fullUrl = `${fullUrl}?${fieldPath}=${paramValue}`; } - // if (fullUrl.startsWith('./')) - // fullUrl = fullUrl.substring(1); this.router.navigateByUrl(fullUrl); } else if ( navigateTo?.targetUrl && From c89cf66e4c6085ed8733338853bcb343699deb87 Mon Sep 17 00:00:00 2001 From: Antoine Hurard Date: Fri, 10 Oct 2025 11:21:28 +0200 Subject: [PATCH 09/11] add logic to duplicate action buttons --- .../edit-action-buttons-modal.component.html | 4 ++++ .../edit-action-buttons-modal.component.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/apps/back-office/src/app/components/edit-action-buttons-modal/edit-action-buttons-modal.component.html b/apps/back-office/src/app/components/edit-action-buttons-modal/edit-action-buttons-modal.component.html index e6daeb6a52..66255f9309 100644 --- a/apps/back-office/src/app/components/edit-action-buttons-modal/edit-action-buttons-modal.component.html +++ b/apps/back-office/src/app/components/edit-action-buttons-modal/edit-action-buttons-modal.component.html @@ -95,6 +95,10 @@

{{ 'common.edit' | translate }} +

- - -
+ + +
{ this.step = data.step; this.actionButtons = data.step.buttons as ActionButton[]; - return this.getFormQuery(this.step.content ?? ''); - }) + const recordId = this.route.snapshot.queryParams.id; + if (recordId) { + return this.getRecordQuery(recordId).pipe( + switchMap((recordResponse) => { + this.record = recordResponse.data.record; + // Then, proceed to fetch the form + return this.getFormQuery(this.step?.content ?? ''); + }) + ); + } + this.record = undefined; + return this.getFormQuery(this.step?.content ?? ''); + }), + takeUntil(this.destroy$) ) .subscribe(({ data, loading }) => { this.handleFormQueryResponse(data, 'step'); @@ -138,8 +153,20 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { switchMap(({ data }) => { this.page = data.page; this.actionButtons = data.page.buttons as ActionButton[]; - return this.getFormQuery(this.page.content ?? ''); - }) + const recordId = this.route.snapshot.queryParams.id; + if (recordId) { + return this.getRecordQuery(recordId).pipe( + switchMap((recordResponse) => { + this.record = recordResponse.data.record; + // Then, proceed to fetch the form + return this.getFormQuery(this.page?.content ?? ''); + }) + ); + } + this.record = undefined; + return this.getFormQuery(this.page?.content ?? ''); + }), + takeUntil(this.destroy$) ) .subscribe(({ data, loading }) => { this.handleFormQueryResponse(data, 'page'); @@ -164,6 +191,21 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { }); } + /** + * Returns query for the given record id + * + * @param id record id + * @returns record query for the given id + */ + private getRecordQuery(id: string) { + return this.apollo.query({ + query: GET_RECORD_BY_ID, + variables: { + id, + }, + }); + } + /** * Handle response for the form query * diff --git a/apps/back-office/src/app/application/pages/form/graphql/queries.ts b/apps/back-office/src/app/application/pages/form/graphql/queries.ts index ed497703f2..f26a190888 100644 --- a/apps/back-office/src/app/application/pages/form/graphql/queries.ts +++ b/apps/back-office/src/app/application/pages/form/graphql/queries.ts @@ -128,3 +128,21 @@ export const GET_SHORT_FORM_BY_ID = gql` } } `; + +/** Graphql request for getting a record by its id */ +export const GET_RECORD_BY_ID = gql` + query GetRecordById($id: ID!) { + record(id: $id) { + id + data + createdAt + createdBy { + name + } + modifiedAt + modifiedBy { + name + } + } + } +`; diff --git a/apps/front-office/src/app/application/pages/form/form.component.html b/apps/front-office/src/app/application/pages/form/form.component.html index 9277a8ac65..ffd325ba66 100644 --- a/apps/front-office/src/app/application/pages/form/form.component.html +++ b/apps/front-office/src/app/application/pages/form/form.component.html @@ -10,15 +10,24 @@ *ngTemplateOutlet="nameTmpl; context: { $implicit: step.name }" > - - - + + + +
- {{ - 'models.record.new' | translate - }} + + {{ 'models.record.new' | translate }}
diff --git a/apps/front-office/src/app/application/pages/form/form.component.ts b/apps/front-office/src/app/application/pages/form/form.component.ts index 1e9643d193..9b7d9bca40 100644 --- a/apps/front-office/src/app/application/pages/form/form.component.ts +++ b/apps/front-office/src/app/application/pages/form/form.component.ts @@ -14,10 +14,12 @@ import { ContextService, Record, BreadcrumbService, + RecordQueryResponse, } from '@oort-front/shared'; import { GET_FORM_BY_ID, GET_PAGE_BY_ID, + GET_RECORD_BY_ID, GET_STEP_BY_ID, } from './graphql/queries'; import { Subscription } from 'rxjs'; @@ -49,8 +51,8 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { public querySubscription?: Subscription; /** Prevents new records to be created */ public hideNewRecord = false; - /** Prevents new records to be created */ - public canCreateRecords = false; + /** Can view form */ + public canViewForm = false; /** Current page */ public page?: Page; /** Current step if nested in workflow */ @@ -59,6 +61,8 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { public isStep = false; /** Form button actions */ public actionButtons: ActionButton[] = []; + /** Current record, optional */ + public record?: Record; /** * Form page. @@ -107,8 +111,20 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { switchMap(({ data }) => { this.step = data.step; this.actionButtons = data.step.buttons as ActionButton[]; + const recordId = this.route.snapshot.queryParams.id; + if (recordId) { + return this.getRecordQuery(recordId).pipe( + switchMap((recordResponse) => { + this.record = recordResponse.data.record; + // Then, proceed to fetch the form + return this.getFormQuery(this.step?.content ?? ''); + }) + ); + } + this.record = undefined; return this.getFormQuery(this.step.content ?? ''); - }) + }), + takeUntil(this.destroy$) ) .subscribe(({ data, loading }) => { this.handleApplicationLoadResponse(data, loading); @@ -125,8 +141,20 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { switchMap(({ data }) => { this.page = data.page; this.actionButtons = data.page.buttons as ActionButton[]; + const recordId = this.route.snapshot.queryParams.id; + if (recordId) { + return this.getRecordQuery(recordId).pipe( + switchMap((recordResponse) => { + this.record = recordResponse.data.record; + // Then, proceed to fetch the form + return this.getFormQuery(this.page?.content ?? ''); + }) + ); + } + this.record = undefined; return this.getFormQuery(this.page.content ?? ''); - }) + }), + takeUntil(this.destroy$) ) .subscribe(({ data, loading }) => { this.handleApplicationLoadResponse(data, loading); @@ -150,11 +178,26 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { }); } + /** + * Returns query for the given record id + * + * @param id record id + * @returns record query for the given id + */ + private getRecordQuery(id: string) { + return this.apollo.query({ + query: GET_RECORD_BY_ID, + variables: { + id, + }, + }); + } + /** * Handles the response for the given form query response data and loading state * * @param {FormQueryResponse} data data retrieved from the form query - * @param {boolean} loading loadin state + * @param {boolean} loading loading state */ private handleApplicationLoadResponse( data: FormQueryResponse, @@ -171,7 +214,8 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { if ( !this.form || this.form.status !== 'active' || - !this.form.canCreateRecords + (this.record && !this.record.canUpdate) || + (!this.record && !this.form.canCreateRecords) ) { this.snackBar.openSnackBar( this.translate.instant('common.notifications.accessNotProvided', { @@ -181,7 +225,7 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { { error: true } ); } else { - this.canCreateRecords = true; + this.canViewForm = true; } this.loading = loading; } diff --git a/apps/front-office/src/app/application/pages/form/graphql/queries.ts b/apps/front-office/src/app/application/pages/form/graphql/queries.ts index 8adb1ea9bd..62056585a1 100644 --- a/apps/front-office/src/app/application/pages/form/graphql/queries.ts +++ b/apps/front-office/src/app/application/pages/form/graphql/queries.ts @@ -67,3 +67,22 @@ export const GET_PAGE_BY_ID = gql` } } `; + +/** Graphql request for getting a record by its id */ +export const GET_RECORD_BY_ID = gql` + query GetRecordById($id: ID!) { + record(id: $id) { + id + data + createdAt + createdBy { + name + } + modifiedAt + modifiedBy { + name + } + canUpdate + } + } +`; diff --git a/libs/shared/src/lib/components/action-button/action-button.component.ts b/libs/shared/src/lib/components/action-button/action-button.component.ts index 2846da62c0..0d4be510d0 100644 --- a/libs/shared/src/lib/components/action-button/action-button.component.ts +++ b/libs/shared/src/lib/components/action-button/action-button.component.ts @@ -316,7 +316,6 @@ export class ActionButtonComponent // Prefill data for addRecord & cloneRecord const loadPrefillData$ = () => { if (this.actionButton.cloneRecord && this.contextId) { - console.log('prepare to clone'); return this.apollo .query({ query: GET_RECORD_BY_ID, diff --git a/libs/shared/src/lib/components/form/form.component.ts b/libs/shared/src/lib/components/form/form.component.ts index 4697afac60..ea55b0a081 100644 --- a/libs/shared/src/lib/components/form/form.component.ts +++ b/libs/shared/src/lib/components/form/form.component.ts @@ -344,6 +344,9 @@ export class FormComponent } else { this.survey.showCompletedPage = true; } + this.snackBar.openSnackBar( + this.translate.instant('components.form.display.submissionMessage') + ); this.save.emit({ completed: true, hideNewRecord: data.addRecord && data.addRecord.form.uniqueRecord, From 4eff9a614fb8f6363fe2e4742245828fe488ac61 Mon Sep 17 00:00:00 2001 From: Antoine Hurard Date: Fri, 10 Oct 2025 16:52:28 +0200 Subject: [PATCH 11/11] finalize ticket --- .../edit-action-button-modal.component.html | 7 +-- .../edit-action-button-modal.component.ts | 12 ++--- .../pages/form/form.component.html | 20 +++++--- .../application/pages/form/form.component.ts | 50 +++++++++++++++++-- .../application/pages/form/graphql/queries.ts | 19 +++++++ 5 files changed, 86 insertions(+), 22 deletions(-) diff --git a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html index 5b60cf0fdb..ad4b0d9704 100644 --- a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html +++ b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.html @@ -346,9 +346,10 @@

{{ 'common.preview' | translate }}

'components.widget.settings.grid.actions.goTo.field.placeholder' | translate " + [filterable]="true" > {{ 'common.preview' | translate }}

{{ 'components.emailBuilder.fields' | translate }}

- + diff --git a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts index 7faf862dfc..11b5eb334b 100644 --- a/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts +++ b/apps/back-office/src/app/components/edit-action-button-modal/edit-action-button-modal.component.ts @@ -126,9 +126,7 @@ export class EditActionButtonModalComponent /** Send notification template list */ public sendNotificationTemplates: Form[] = []; /** Fields, of current page context resource, if any */ - public sendNotificationFields: any[] = []; - /** All fields of current context resource for targetPage field selection */ - public availableFields: any[] = []; + public contextResourceFields: any[] = []; /** Available pages from the application for targetPage selection */ public pages: any[] = []; @@ -193,16 +191,13 @@ export class EditActionButtonModalComponent }) .subscribe({ next: ({ data }) => { - const queryTemp: any = data.resource; - const newData = this.queryBuilder.getFields(queryTemp.queryName); this.editRecordTemplates = data.resource.forms ?? []; - this.sendNotificationFields = this.queryBuilder.getFields( - data.resource?.queryName as string + this.contextResourceFields = this.queryBuilder.getFields( + data.resource.queryName as string ); this.resourceFields = data.resource.fields.filter((f: any) => ['resource', 'resources'].includes(f.type) ); - this.availableFields = newData; }, }); } @@ -545,7 +540,6 @@ export class EditActionButtonModalComponent this.selectedResource = data.resource; this.addRecordTemplates = data.resource.forms ?? []; this.addRecordFields = data.resource.fields ?? []; - this.availableFields = data.resource.fields ?? []; }, }); } diff --git a/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/form.component.html b/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/form.component.html index c0a009e035..964c981c0c 100644 --- a/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/form.component.html +++ b/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/form.component.html @@ -1,12 +1,20 @@ - - - + + +
- {{ - 'models.record.new' | translate - }} + + {{ 'models.record.new' | translate }}
diff --git a/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/form.component.ts b/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/form.component.ts index 1772c7fcc0..c3dd6b4324 100644 --- a/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/form.component.ts +++ b/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/form.component.ts @@ -14,10 +14,12 @@ import { Record, ContextService, BreadcrumbService, + RecordQueryResponse, } from '@oort-front/shared'; import { GET_FORM_BY_ID, GET_PAGE_BY_ID, + GET_RECORD_BY_ID, GET_STEP_BY_ID, } from './graphql/queries'; import { Subscription } from 'rxjs'; @@ -49,8 +51,8 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { public querySubscription?: Subscription; /** Prevents new records to be created */ public hideNewRecord = false; - /** Prevents new records to be created */ - public canCreateRecords = false; + /** Can view form */ + public canViewForm = false; /** Current page */ public page?: Page; /** Current step if nested in workflow */ @@ -59,6 +61,8 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { public isStep = false; /** Form button actions */ public actionButtons: ActionButton[] = []; + /** Current record, optional */ + public record?: Record; /** * Form page. @@ -107,6 +111,17 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { switchMap(({ data }) => { this.step = data.step; this.actionButtons = data.step.buttons as ActionButton[]; + const recordId = this.route.snapshot.queryParams.id; + if (recordId) { + return this.getRecordQuery(recordId).pipe( + switchMap((recordResponse) => { + this.record = recordResponse.data.record; + // Then, proceed to fetch the form + return this.getFormQuery(this.step?.content ?? ''); + }) + ); + } + this.record = undefined; return this.getFormQuery(this.step.content ?? ''); }) ) @@ -125,6 +140,17 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { switchMap(({ data }) => { this.page = data.page; this.actionButtons = data.page.buttons as ActionButton[]; + const recordId = this.route.snapshot.queryParams.id; + if (recordId) { + return this.getRecordQuery(recordId).pipe( + switchMap((recordResponse) => { + this.record = recordResponse.data.record; + // Then, proceed to fetch the form + return this.getFormQuery(this.page?.content ?? ''); + }) + ); + } + this.record = undefined; return this.getFormQuery(this.page.content ?? ''); }) ) @@ -150,6 +176,21 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { }); } + /** + * Returns query for the given record id + * + * @param id record id + * @returns record query for the given id + */ + private getRecordQuery(id: string) { + return this.apollo.query({ + query: GET_RECORD_BY_ID, + variables: { + id, + }, + }); + } + /** * Handles the response for the given form query response data and loading state * @@ -171,7 +212,8 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { if ( !this.form || this.form.status !== 'active' || - !this.form.canCreateRecords + (this.record && !this.record.canUpdate) || + (!this.record && !this.form.canCreateRecords) ) { this.snackBar.openSnackBar( this.translate.instant('common.notifications.accessNotProvided', { @@ -181,7 +223,7 @@ export class FormComponent extends UnsubscribeComponent implements OnInit { { error: true } ); } else { - this.canCreateRecords = true; + this.canViewForm = true; } this.loading = loading; } diff --git a/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/graphql/queries.ts b/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/graphql/queries.ts index fa01fbc8d8..c5fa678446 100644 --- a/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/graphql/queries.ts +++ b/apps/web-widgets/src/app/widgets/app-widget/application/pages/form/graphql/queries.ts @@ -65,3 +65,22 @@ export const GET_PAGE_BY_ID = gql` } } `; + +/** Graphql request for getting a record by its id */ +export const GET_RECORD_BY_ID = gql` + query GetRecordById($id: ID!) { + record(id: $id) { + id + data + createdAt + createdBy { + name + } + modifiedAt + modifiedBy { + name + } + canUpdate + } + } +`;