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..10f185a88a 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 @@ -617,43 +617,29 @@ export class CoreGridComponent .subscribe(({ data }) => { const queryName = data.resource.singleQueryName; if (queryName) { - const query = this.queryBuilder.buildQuery( - { - query: { - ...this.settings.query, - name: queryName, - }, - }, - true + const editedRecord = get(data, queryName); + const dataItem = this.gridData.data.find( + (x) => x.id === item.id ); - if (query) { - this.apollo - .query({ - query, - variables: { - id: item.id, - data: editedData, - }, - }) - .pipe(takeUntil(this.destroy$)) - .subscribe(({ data }) => { - const dataItem = this.gridData.data.find( - (x) => x.id === item.id - ); - // Update data item element - Object.assign(dataItem, get(data, queryName)); - // Update data item raw value ( used by inline edition ) - dataItem._meta.raw = editedData; - item.saved = false; - const index = this.updatedItems.findIndex( - (x) => x.id === item.id - ); - this.updatedItems.splice(index, 1, { - id: item.id, - ...editedData, - }); - this.loadItems(); - }); + if (dataItem) { + Object.assign(dataItem, editedRecord); + dataItem._meta = dataItem._meta || {}; + const currentRaw = dataItem._meta.raw || {}; + dataItem._meta.raw = { ...currentRaw, ...editedData }; + item.saved = false; + const idx = this.updatedItems.findIndex( + (x) => x.id === item.id + ); + const mergedForSave = { + id: item.id, + ...currentRaw, + ...editedData, + }; + if (idx >= 0) { + this.updatedItems.splice(idx, 1, mergedForSave); + } else { + this.updatedItems.push(mergedForSave); + } } } }); diff --git a/libs/shared/src/lib/survey/components/resources.ts b/libs/shared/src/lib/survey/components/resources.ts index 6b51007a3a..fd420a796e 100644 --- a/libs/shared/src/lib/survey/components/resources.ts +++ b/libs/shared/src/lib/survey/components/resources.ts @@ -26,6 +26,7 @@ import { QuestionResource } from '../types'; import { buildAddButton, buildSearchButton, + buildInlineAddButton, processNewCreatedRecords, setUpActionsButtonWrapper, } from './utils'; @@ -164,6 +165,13 @@ export const init = ( } }; + const visibleIfResourceAndDisplayGridAndAddRecord = (obj: any) => { + if (!obj || !obj.resource || !obj.displayAsGrid || !obj.addRecord) { + return false; + } + return true; + }; + const component = { name: 'resources', title: 'Resources', @@ -256,7 +264,7 @@ export const init = ( category: 'Custom Questions', dependsOn: 'resource', visibleIf: visibleIfResource, - visibleIndex: 3, + visibleIndex: 2, }); Serializer.addProperty('resources', { name: 'addRecord:boolean', @@ -266,6 +274,14 @@ export const init = ( visibleIf: visibleIfResource, visibleIndex: 2, }); + Serializer.addProperty('resources', { + name: 'addInlineRecord:boolean', + displayName: 'Add inline records', + category: 'Custom Questions', + dependsOn: ['resource', 'displayAsGrid', 'addRecord'], + visibleIf: visibleIfResourceAndDisplayGridAndAddRecord, + visibleIndex: 4, + }); Serializer.addProperty('resources', { name: 'canDelete:boolean', displayName: 'Delete records', @@ -557,12 +573,51 @@ export const init = ( ); actionsButtons.appendChild(addBtn); + const inlineAddBtn = buildInlineAddButton( + question, + true, + ngZone, + document, + apollo + ); + actionsButtons.appendChild(inlineAddBtn); + + question.registerFunctionOnPropertyValueChanged( + 'addInlineRecord', + () => { + inlineAddBtn.style.display = + question.addInlineRecord && + question.displayAsGrid && + question.addRecord && + question.addTemplate && + !question.isReadOnly + ? '' + : 'none'; + } + ); + inlineAddBtn.style.display = + question.addInlineRecord && + question.displayAsGrid && + question.addRecord && + question.addTemplate && + !question.isReadOnly + ? '' + : 'none'; + // actionsButtons.style.display = ((!question.addRecord || !question.addTemplate) && !question.gridFieldsSettings) ? 'none' : ''; question.registerFunctionOnPropertyValueChanged( 'addTemplate', () => { addBtn.style.display = question.addRecord && question.addTemplate ? '' : 'none'; + inlineAddBtn.style.display = + question.addInlineRecord && + question.displayAsGrid && + question.addRecord && + question.addTemplate && + !question.isReadOnly + ? '' + : 'none'; } ); question.registerFunctionOnPropertyValueChanged('addRecord', () => { @@ -572,6 +627,15 @@ export const init = ( !question.isReadOnly ? '' : 'none'; + + inlineAddBtn.style.display = + question.addInlineRecord && + question.displayAsGrid && + question.addRecord && + question.addTemplate && + !question.isReadOnly + ? '' + : 'none'; }); } } @@ -582,6 +646,19 @@ export const init = ( if (question.resource && question.canSearch) { searchBtn.style.display = 'block'; } + const inlineAddBtn = parentElement.querySelector( + '#actionsButtons button[data-action="inline-add"]' + ); + if (inlineAddBtn) { + (inlineAddBtn as HTMLButtonElement).style.display = + question.addInlineRecord && + question.displayAsGrid && + question.addRecord && + question.addTemplate && + !question.isReadOnly + ? '' + : 'none'; + } }); question.registerFunctionOnPropertyValueChanged('canSearch', () => { if (question.displayAsGrid) { @@ -618,6 +695,19 @@ export const init = ( searchBtn.style.display = 'block'; } } + const inlineAddBtn = parentElement.querySelector( + '#actionsButtons button[data-action="inline-add"]' + ); + if (inlineAddBtn) { + (inlineAddBtn as HTMLButtonElement).style.display = + question.addInlineRecord && + question.displayAsGrid && + question.addRecord && + question.addTemplate && + !question.isReadOnly + ? '' + : 'none'; + } }); question.registerFunctionOnPropertiesValueChanged( [ @@ -680,6 +770,9 @@ export const init = ( setGridInputs(grid.instance, question); question.survey?.onValueChanged.add((_: any, options: any) => { if (options.name === question.name) { + if ((grid.instance as CoreGridComponent).hasChanges) { + return; + } setGridInputs(grid.instance, question); } }); diff --git a/libs/shared/src/lib/survey/components/utils.ts b/libs/shared/src/lib/survey/components/utils.ts index 0b1630ad57..17e4a61f93 100644 --- a/libs/shared/src/lib/survey/components/utils.ts +++ b/libs/shared/src/lib/survey/components/utils.ts @@ -5,6 +5,9 @@ import { UntypedFormControl } from '@angular/forms'; import { CompositeFilterDescriptor } from '@progress/kendo-data-query'; import { SurveyModel, surveyLocalization } from 'survey-core'; import { Question } from '../types'; +import { Apollo } from 'apollo-angular'; +import { ADD_RECORD } from '../../components/form-modal/graphql/mutations'; +import { firstValueFrom } from 'rxjs'; /** * Build the search button for resource and resources components @@ -218,6 +221,113 @@ export const buildAddButton = ( return addButton; }; +/** + * Build the inline add button for resource and resources components + * + * @param question The question object + * @param multiselect Indicate if we need multiselect + * @param ngZone Angular Service to execute code inside Angular environment + * @param document Document + * @param apollo Apollo client + * @returns The button DOM element + */ +export const buildInlineAddButton = ( + question: Question, + multiselect: boolean, + ngZone: NgZone, + document: Document, + apollo: Apollo +): any => { + const inlineAddButton = document.createElement('button'); + inlineAddButton.innerText = surveyLocalization.getString( + 'oort:addInlineRecord', + (question.survey as SurveyModel).locale + ); + inlineAddButton.className = 'sd-btn !px-3 !py-1'; + inlineAddButton.setAttribute('data-action', 'inline-add'); + + if ( + question.addInlineRecord && + question.displayAsGrid && + question.addRecord && + question.addTemplate && + !question.isReadOnly + ) { + inlineAddButton.onclick = async () => { + const result = await firstValueFrom( + apollo.mutate<{ addRecord: any }>({ + mutation: ADD_RECORD, + variables: { + form: question.addTemplate, + data: {}, + display: false, + }, + }) + ); + + const created = result.data?.addRecord; + if (!created) return; + + ngZone.run(() => { + const createdId = created.id; + question.template = question.addTemplate; + question.draftData = { + ...question.draftData, + [createdId]: created.data || {}, + }; + + if (multiselect) { + const newItem = { + value: createdId, + text: created.data?.[(question as any).displayField], + } as any; + question.contentQuestion.choices = [ + newItem, + ...question.contentQuestion.choices, + ]; + question.newCreatedRecords = question.newCreatedRecords + ? question.newCreatedRecords.concat(createdId) + : [createdId]; + question.value = Array.isArray(question.value) + ? (question.value as any[]).concat(createdId) + : question.value + ? [question.value, createdId] + : [createdId]; + } else { + const newItem = { + value: createdId, + text: created.data?.[(question as any).displayField], + } as any; + question.contentQuestion.choices = [ + newItem, + ...question.contentQuestion.choices, + ]; + question.newCreatedRecords = createdId; + question.value = createdId; + } + }); + }; + } + + inlineAddButton.style.display = + question.addInlineRecord && + question.displayAsGrid && + question.addRecord && + question.addTemplate && + !question.isReadOnly + ? '' + : 'none'; + + question.registerFunctionOnPropertyValueChanged( + 'readOnly', + (value: boolean) => { + inlineAddButton.style.display = value ? 'none' : ''; + } + ); + + return inlineAddButton; +}; + /** * Updates the newCreatedRecords for resource and resources questions * diff --git a/libs/shared/src/lib/survey/localization.ts b/libs/shared/src/lib/survey/localization.ts index e361a88a68..2214c1d575 100644 --- a/libs/shared/src/lib/survey/localization.ts +++ b/libs/shared/src/lib/survey/localization.ts @@ -9,6 +9,13 @@ const SURVEY_LOCALIZABLE_STRINGS = [ fr: 'Ajouter un enregistrement', }, }, + { + key: 'addInlineRecord', + locales: { + en: 'Add inline record', + fr: 'Ajouter un enregistrement en ligne', + }, + }, { key: 'fileLimitations', locales: { diff --git a/libs/shared/src/lib/survey/types.ts b/libs/shared/src/lib/survey/types.ts index 73ae853801..105a516e5a 100644 --- a/libs/shared/src/lib/survey/types.ts +++ b/libs/shared/src/lib/survey/types.ts @@ -70,6 +70,7 @@ export interface QuestionResource displayField: null | string; relatedName?: string; addRecord?: boolean; + addInlineRecord?: boolean; canSearch?: boolean; addTemplate?: any; placeholder?: string;