diff --git a/apps/smart-forms-app/e2e/prepop.spec.ts b/apps/smart-forms-app/e2e/prepop.spec.ts index 0788db80a..5e80e58a6 100644 --- a/apps/smart-forms-app/e2e/prepop.spec.ts +++ b/apps/smart-forms-app/e2e/prepop.spec.ts @@ -151,4 +151,16 @@ test('pre-pop to test terminology resolving logic', async ({ page }) => { .getByTestId('q-item-choice-select-answer-value-set-box') .locator(`#${genderAvsContainedValueLinkId}`) ).toHaveValue('Female'); + + const medicalHistoryConditionValueLinkId = 'medical-history-condition'; + + const elements = await page.locator(`#${medicalHistoryConditionValueLinkId}`).all(); + for (const element of elements) { + const inputValue = await element.inputValue(); + + // Test if the input values contains at least one alphabetic character + // If it's fully numeric, it means the valueCoding.code is pre-populated instead of valueCoding.display + // the logic in sdc-populate, automatically $lookup and assigns display to codings that lack them, which means that part is failing in this case + expect(/^\d+$/.test(inputValue)).toBeFalsy(); + } }); diff --git a/apps/smart-forms-app/package.json b/apps/smart-forms-app/package.json index 6b5f435aa..fd53740f6 100644 --- a/apps/smart-forms-app/package.json +++ b/apps/smart-forms-app/package.json @@ -27,8 +27,8 @@ "homepage": "https://github.com/aehrc/smart-forms#readme", "dependencies": { "@aehrc/sdc-assemble": "^1.2.0", - "@aehrc/sdc-populate": "^2.2.1", - "@aehrc/smart-forms-renderer": "^0.35.5", + "@aehrc/sdc-populate": "^2.2.2", + "@aehrc/smart-forms-renderer": "^0.35.6", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@fontsource/material-icons": "^5.0.16", diff --git a/apps/smart-forms-app/src/features/repopulate/components/RepopulateGridGroup.tsx b/apps/smart-forms-app/src/features/repopulate/components/RepopulateGridGroup.tsx index d4cbfd17e..5cfdb705f 100644 --- a/apps/smart-forms-app/src/features/repopulate/components/RepopulateGridGroup.tsx +++ b/apps/smart-forms-app/src/features/repopulate/components/RepopulateGridGroup.tsx @@ -22,7 +22,7 @@ import cloneDeep from 'lodash.clonedeep'; interface RepopulateGridGroupProps { qItem: QuestionnaireItem; - newQRItem: QuestionnaireResponseItem; + newQRItem?: QuestionnaireResponseItem; oldQRItem?: QuestionnaireResponseItem; } @@ -34,7 +34,8 @@ function RepopulateGridGroup(props: RepopulateGridGroupProps) { oldQRItem ?? cloneDeep({ ...newQRItem, - item: newQRItem.item?.map((item) => ({ + linkId: newQRItem?.linkId ?? '', + item: newQRItem?.item?.map((item) => ({ ...item, item: item.item?.map((subItem) => ({ ...subItem, diff --git a/apps/smart-forms-app/src/features/repopulate/components/RepopulateGroupTable.tsx b/apps/smart-forms-app/src/features/repopulate/components/RepopulateGroupTable.tsx index 2ecb66e9a..e81c49236 100644 --- a/apps/smart-forms-app/src/features/repopulate/components/RepopulateGroupTable.tsx +++ b/apps/smart-forms-app/src/features/repopulate/components/RepopulateGroupTable.tsx @@ -21,7 +21,7 @@ import { GroupTable } from '@aehrc/smart-forms-renderer'; interface RepopulateRepeatGroupProps { qItem: QuestionnaireItem; - newQRItems: QuestionnaireResponseItem[]; + newQRItems?: QuestionnaireResponseItem[]; oldQRItems?: QuestionnaireResponseItem[]; } @@ -49,7 +49,7 @@ function RepopulateGroupTable(props: RepopulateRepeatGroupProps) { void; } diff --git a/apps/smart-forms-app/src/features/repopulate/components/RepopulateRepeatGroup.tsx b/apps/smart-forms-app/src/features/repopulate/components/RepopulateRepeatGroup.tsx index 58911ec0a..03cbad2ca 100644 --- a/apps/smart-forms-app/src/features/repopulate/components/RepopulateRepeatGroup.tsx +++ b/apps/smart-forms-app/src/features/repopulate/components/RepopulateRepeatGroup.tsx @@ -21,7 +21,7 @@ import { RepeatGroup } from '@aehrc/smart-forms-renderer'; interface RepopulateRepeatGroupProps { qItem: QuestionnaireItem; - newQRItems: QuestionnaireResponseItem[]; + newQRItems?: QuestionnaireResponseItem[]; oldQRItems?: QuestionnaireResponseItem[]; } @@ -49,7 +49,7 @@ function RepopulateRepeatGroup(props: RepopulateRepeatGroupProps) { void 0} diff --git a/apps/smart-forms-app/src/features/repopulate/components/RepopulateSingleItem.tsx b/apps/smart-forms-app/src/features/repopulate/components/RepopulateSingleItem.tsx index 1ef9985cd..3fa2b373d 100644 --- a/apps/smart-forms-app/src/features/repopulate/components/RepopulateSingleItem.tsx +++ b/apps/smart-forms-app/src/features/repopulate/components/RepopulateSingleItem.tsx @@ -22,7 +22,7 @@ import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4'; interface RepopulateSingleItemProps { qItem: QuestionnaireItem; oldQRItem?: QuestionnaireResponseItem; - newQRItem: QuestionnaireResponseItem; + newQRItem?: QuestionnaireResponseItem; } function RepopulateSingleItem(props: RepopulateSingleItemProps) { @@ -58,7 +58,7 @@ function RepopulateSingleItem(props: RepopulateSingleItemProps) { >; + promise: Promise; oldCoding: Coding; newCoding?: Coding; } diff --git a/packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/addDisplayToCodings.ts b/packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/addDisplayToCodings.ts index fd67c5d16..b38ffee6a 100644 --- a/packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/addDisplayToCodings.ts +++ b/packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/addDisplayToCodings.ts @@ -20,6 +20,7 @@ import type { InitialExpression } from '../interfaces/expressions.interface'; import type { Coding } from 'fhir/r4'; +import type { LookupResponse } from '../api/lookupCodeSystem'; import { getCodeSystemLookupPromise, lookupResponseIsValid } from '../api/lookupCodeSystem'; import type { FetchResourceCallback } from '../interfaces'; @@ -89,8 +90,22 @@ export async function resolveLookupPromises( continue; } - const lookupResult = settledPromise.value; - if (!lookupResponseIsValid(lookupResult)) { + let lookupResult: LookupResponse | null = null; + + // Get lookupResult from response (fhirClient scenario) + if (lookupResponseIsValid(settledPromise.value)) { + lookupResult = settledPromise.value; + } + // Fallback to get valueSet from response.data (axios scenario) + if ( + !lookupResult && + settledPromise.value.data && + lookupResponseIsValid(settledPromise.value.data) + ) { + lookupResult = settledPromise.value.data; + } + + if (!lookupResult) { continue; } diff --git a/packages/smart-forms-renderer/package.json b/packages/smart-forms-renderer/package.json index 4c1479c79..e92a164ea 100644 --- a/packages/smart-forms-renderer/package.json +++ b/packages/smart-forms-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@aehrc/smart-forms-renderer", - "version": "0.35.5", + "version": "0.35.6", "description": "FHIR Structured Data Captured (SDC) rendering engine for Smart Forms", "main": "lib/index.js", "scripts": { @@ -27,7 +27,7 @@ }, "homepage": "https://github.com/aehrc/smart-forms#readme", "dependencies": { - "@aehrc/sdc-populate": "^2.2.1", + "@aehrc/sdc-populate": "^2.2.2", "@iconify/react": "^4.1.1", "dayjs": "^1.11.10", "deep-diff": "^1.0.2", @@ -37,6 +37,8 @@ "js-base64": "^3.7.7", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", + "lodash.difference": "^4.5.0", + "lodash.intersection": "^4.4.0", "nanoid": "^5.0.1", "react-beautiful-dnd": "^13.1.1", "react-dnd": "^16.0.1", diff --git a/packages/smart-forms-renderer/src/interfaces/initialExpression.interface.ts b/packages/smart-forms-renderer/src/interfaces/initialExpression.interface.ts new file mode 100644 index 000000000..d4b7a70bb --- /dev/null +++ b/packages/smart-forms-renderer/src/interfaces/initialExpression.interface.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * InitialExpression interface + * + * @property expression - InitialExpression FHIRPath expression + */ +export interface InitialExpression { + expression: string; +} diff --git a/packages/smart-forms-renderer/src/interfaces/questionnaireStore.interface.ts b/packages/smart-forms-renderer/src/interfaces/questionnaireStore.interface.ts index 4558f31e6..d0ea6b34b 100644 --- a/packages/smart-forms-renderer/src/interfaces/questionnaireStore.interface.ts +++ b/packages/smart-forms-renderer/src/interfaces/questionnaireStore.interface.ts @@ -22,6 +22,7 @@ import type { EnableWhenExpressions, EnableWhenItems } from './enableWhen.interf import type { CalculatedExpression } from './calculatedExpression.interface'; import type { AnswerExpression } from './answerExpression.interface'; import type { Coding, QuestionnaireItemAnswerOption } from 'fhir/r4'; +import type { InitialExpression } from './initialExpression.interface'; export interface QuestionnaireModel { itemTypes: Record; @@ -31,6 +32,7 @@ export interface QuestionnaireModel { enableWhenItems: EnableWhenItems; enableWhenExpressions: EnableWhenExpressions; calculatedExpressions: Record; + initialExpressions: Record; answerExpressions: Record; answerOptions: Record; processedValueSetCodings: Record; diff --git a/packages/smart-forms-renderer/src/stores/questionnaireStore.ts b/packages/smart-forms-renderer/src/stores/questionnaireStore.ts index 2e2fbaa8a..34c3844cb 100644 --- a/packages/smart-forms-renderer/src/stores/questionnaireStore.ts +++ b/packages/smart-forms-renderer/src/stores/questionnaireStore.ts @@ -47,6 +47,7 @@ import { mutateRepeatEnableWhenExpressionInstances } from '../utils/enableWhenEx import { questionnaireResponseStore } from './questionnaireResponseStore'; import { createQuestionnaireResponseItemMap } from '../utils/questionnaireResponseStoreUtils/updatableResponseItems'; import { insertCompleteAnswerOptionsIntoQuestionnaire } from '../utils/questionnaireStoreUtils/insertAnswerOptions'; +import type { InitialExpression } from '../interfaces/initialExpression.interface'; /** * QuestionnaireStore properties and methods @@ -100,6 +101,7 @@ export interface QuestionnaireStoreType { enableWhenIsActivated: boolean; enableWhenExpressions: EnableWhenExpressions; calculatedExpressions: Record; + initialExpressions: Record; answerExpressions: Record; processedValueSetCodings: Record; processedValueSetUrls: Record; @@ -156,6 +158,7 @@ export const questionnaireStore = createStore()((set, ge variables: { fhirPathVariables: {}, xFhirQueryVariables: {} }, launchContexts: {}, calculatedExpressions: {}, + initialExpressions: {}, enableWhenExpressions: { singleExpressions: {}, repeatExpressions: {} }, answerExpressions: {}, enableWhenItems: { singleItems: {}, repeatItems: {} }, @@ -216,6 +219,7 @@ export const questionnaireStore = createStore()((set, ge enableWhenLinkedQuestions: initialEnableWhenLinkedQuestions, enableWhenExpressions: initialEnableWhenExpressions, calculatedExpressions: initialCalculatedExpressions, + initialExpressions: questionnaireModel.initialExpressions, answerExpressions: questionnaireModel.answerExpressions, processedValueSetCodings: questionnaireModel.processedValueSetCodings, processedValueSetUrls: questionnaireModel.processedValueSetUrls, @@ -235,6 +239,7 @@ export const questionnaireStore = createStore()((set, ge enableWhenLinkedQuestions: {}, enableWhenExpressions: { singleExpressions: {}, repeatExpressions: {} }, calculatedExpressions: {}, + initialExpressions: {}, answerExpressions: {}, processedValueSetCodings: {}, processedValueSetUrls: {}, diff --git a/packages/smart-forms-renderer/src/stories/assets/questionnaires/QPrePopTester.ts b/packages/smart-forms-renderer/src/stories/assets/questionnaires/QPrePopTester.ts index 1797f082f..8d83be3f2 100644 --- a/packages/smart-forms-renderer/src/stories/assets/questionnaires/QPrePopTester.ts +++ b/packages/smart-forms-renderer/src/stories/assets/questionnaires/QPrePopTester.ts @@ -127,9 +127,43 @@ export const qSelectivePrePopTester: Questionnaire = { } ] } + }, + { + resourceType: 'ValueSet', + id: 'MedicalHistory', + url: 'https://smartforms.csiro.au/ig/ValueSet/MedicalHistory', + name: 'MedicalHistory', + title: 'Medical History', + status: 'draft', + experimental: false, + description: + 'The Medical History value set includes values that may be used to represent medical history, operations and hospital admissions.', + compose: { + include: [ + { + system: 'http://snomed.info/sct', + filter: [ + { + property: 'constraint', + op: '=', + value: + '^32570581000036105|Problem/Diagnosis reference set| OR ^32570141000036105|Procedure foundation reference set|' + } + ] + } + ] + } } ], extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/variable', + valueExpression: { + name: 'Condition', + language: 'application/x-fhir-query', + expression: 'Condition?patient={{%patient.id}}' + } + }, { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext', extension: [ @@ -196,6 +230,89 @@ export const qSelectivePrePopTester: Questionnaire = { type: 'choice', repeats: false, answerValueSet: 'http://hl7.org/fhir/ValueSet/administrative-gender' + }, + { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl', + valueCodeableConcept: { + coding: [ + { + system: 'http://hl7.org/fhir/questionnaire-item-control', + code: 'gtable' + } + ] + } + }, + { + url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemPopulationContext', + valueExpression: { + name: 'ConditionRepeat', + language: 'text/fhirpath', + expression: + "%Condition.entry.resource.where(category.coding.exists(code='problem-list-item'))" + } + } + ], + linkId: 'medical-history', + text: 'Medical history and current problems list', + type: 'group', + repeats: true, + item: [ + { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl', + valueCodeableConcept: { + coding: [ + { + system: 'http://hl7.org/fhir/questionnaire-item-control', + code: 'autocomplete' + } + ] + } + }, + { + url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression', + valueExpression: { + language: 'text/fhirpath', + expression: + "%ConditionRepeat.code.select((coding.where(system='http://snomed.info/sct') | coding.where(system!='http://snomed.info/sct').first() | text ).first())" + } + } + ], + linkId: 'medical-history-condition', + text: 'Condition', + type: 'open-choice', + answerValueSet: '#MedicalHistory' + }, + { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl', + valueCodeableConcept: { + coding: [ + { + system: 'http://hl7.org/fhir/questionnaire-item-control', + code: 'drop-down' + } + ] + } + }, + { + url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression', + valueExpression: { + language: 'text/fhirpath', + expression: '%ConditionRepeat.clinicalStatus.coding' + } + } + ], + linkId: 'medical-history-status', + text: 'Clinical Status', + type: 'choice', + answerValueSet: 'http://hl7.org/fhir/ValueSet/condition-clinical' + } + ] } ] }; diff --git a/packages/smart-forms-renderer/src/utils/getExpressionsFromItem.ts b/packages/smart-forms-renderer/src/utils/getExpressionsFromItem.ts index afafe0f76..609dfc025 100644 --- a/packages/smart-forms-renderer/src/utils/getExpressionsFromItem.ts +++ b/packages/smart-forms-renderer/src/utils/getExpressionsFromItem.ts @@ -18,6 +18,25 @@ import type { Expression, Extension, QuestionnaireItem } from 'fhir/r4'; import type { CalculatedExpression } from '../interfaces/calculatedExpression.interface'; +/** + * Get enableWhenExpression.valueExpression if its present in item + * + * @author Sean Fong + */ +export function getInitialExpression(qItem: QuestionnaireItem): Expression | null { + const initialExpression = qItem.extension?.find( + (extension: Extension) => + extension.url === + 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression' && + extension.valueExpression?.language === 'text/fhirpath' + ); + + if (initialExpression?.valueExpression) { + return initialExpression.valueExpression; + } + return null; +} + /** * Get enableWhenExpression.valueExpression if its present in item * diff --git a/packages/smart-forms-renderer/src/utils/misc.ts b/packages/smart-forms-renderer/src/utils/misc.ts index e044fbe96..35b3d243f 100644 --- a/packages/smart-forms-renderer/src/utils/misc.ts +++ b/packages/smart-forms-renderer/src/utils/misc.ts @@ -18,6 +18,8 @@ // TODO to be imported into sdc-fhir-helpers import type { Questionnaire, QuestionnaireItem } from 'fhir/r4'; +import type { Tabs } from '../interfaces'; +import { getShortText } from './itemControl'; export function getQuestionnaireItem( questionnaire: Questionnaire, @@ -158,3 +160,58 @@ function getRepeatGroupParentItemRecursive( // No matching repeat group parent item found in the current item or its child items, return null return null; } + +/* + Used for getting the tab section heading for Smart Form's re-population + */ +export function getSectionHeading( + questionnaire: Questionnaire, + targetLinkId: string, + tabs: Tabs +): string | null { + // Search through the top level items recursively + const topLevelQItems = questionnaire.item; + if (topLevelQItems) { + for (const topLevelQItem of topLevelQItems) { + const heading = topLevelQItem.text ?? null; + + const foundQItem = getSectionHeadingRecursive(topLevelQItem, targetLinkId, heading, tabs); + if (foundQItem) { + return foundQItem; + } + } + } + + // No heading found in the questionnaire, return null + return null; +} + +export function getSectionHeadingRecursive( + qItem: QuestionnaireItem, + targetLinkId: string, + heading: string | null, + tabs: Tabs +): string | null { + // Target linkId found in current item + if (qItem.linkId === targetLinkId) { + return heading; + } + + // Search through its child items recursively + const childQItems = qItem.item; + if (childQItems) { + const isTab = !!tabs[qItem.linkId]; + if (isTab) { + heading = getShortText(qItem) ?? qItem.text ?? null; + } + for (const childQItem of childQItems) { + const foundHeading = getSectionHeadingRecursive(childQItem, targetLinkId, heading, tabs); + if (foundHeading) { + return foundHeading; + } + } + } + + // No heading found in the current item or its child items, return null + return null; +} diff --git a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts index da5c951ed..f3169d07c 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts @@ -66,6 +66,7 @@ export async function createQuestionnaireModel( enableWhenItems, enableWhenExpressions, calculatedExpressions, + initialExpressions, answerExpressions, answerOptions } = extractOtherExtensionsResult; @@ -102,6 +103,7 @@ export async function createQuestionnaireModel( enableWhenItems, enableWhenExpressions, calculatedExpressions, + initialExpressions, answerExpressions, answerOptions: completeAnswerOptions, processedValueSetCodings, @@ -117,6 +119,7 @@ function createEmptyModel(): QuestionnaireModel { variables: { fhirPathVariables: {}, xFhirQueryVariables: {} }, launchContexts: {}, calculatedExpressions: {}, + initialExpressions: {}, enableWhenExpressions: { singleExpressions: {}, repeatExpressions: {} }, answerExpressions: {}, answerOptions: {}, diff --git a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts index 771867758..960e05958 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts @@ -46,14 +46,17 @@ import { evaluateEnableWhenRepeatExpressionInstance } from '../enableWhenExpress import { getAnswerExpression, getCalculatedExpressions, - getEnableWhenExpression + getEnableWhenExpression, + getInitialExpression } from '../getExpressionsFromItem'; +import type { InitialExpression } from '../../interfaces/initialExpression.interface'; interface ReturnParamsRecursive { variables: Variables; enableWhenItems: EnableWhenItems; enableWhenExpressions: EnableWhenExpressions; calculatedExpressions: Record; + initialExpressions: Record; answerExpressions: Record; valueSetPromises: Record; answerOptions: Record; @@ -71,6 +74,7 @@ export function extractOtherExtensions( repeatExpressions: {} }; const calculatedExpressions: Record = {}; + const initialExpressions: Record = {}; const answerExpressions: Record = {}; const answerOptions: Record = {}; @@ -83,6 +87,7 @@ export function extractOtherExtensions( repeatExpressions: {} }, calculatedExpressions: {}, + initialExpressions: {}, answerExpressions: {}, answerOptions: {}, valueSetPromises: valueSetPromises @@ -98,6 +103,7 @@ export function extractOtherExtensions( enableWhenItems, enableWhenExpressions, calculatedExpressions, + initialExpressions, answerExpressions, answerOptions, valueSetPromises, @@ -111,6 +117,7 @@ export function extractOtherExtensions( enableWhenItems, enableWhenExpressions, calculatedExpressions, + initialExpressions, answerExpressions, answerOptions, valueSetPromises @@ -124,6 +131,7 @@ interface extractExtensionsFromItemRecursiveParams { enableWhenItems: EnableWhenItems; enableWhenExpressions: EnableWhenExpressions; calculatedExpressions: Record; + initialExpressions: Record; answerExpressions: Record; answerOptions: Record; valueSetPromises: Record; @@ -141,6 +149,7 @@ function extractExtensionsFromItemRecursive( enableWhenItems, enableWhenExpressions, calculatedExpressions, + initialExpressions, answerExpressions, answerOptions, valueSetPromises, @@ -201,6 +210,14 @@ function extractExtensionsFromItemRecursive( calculatedExpressions[item.linkId] = calculatedExpressionsOfItem; } + // Get initialExpressions + const initialExpression = getInitialExpression(item); + if (initialExpression) { + initialExpressions[item.linkId] = { + expression: `${initialExpression.expression}` + }; + } + // Get answerExpressions const answerExpression = getAnswerExpression(item); if (answerExpression) { @@ -242,6 +259,7 @@ function extractExtensionsFromItemRecursive( enableWhenItems, enableWhenExpressions, calculatedExpressions, + initialExpressions, answerExpressions, answerOptions, valueSetPromises diff --git a/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts b/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts index 15c767505..32b80bda5 100644 --- a/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts +++ b/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts @@ -163,6 +163,10 @@ function constructGroupItem( return null; } + if (!itemToRepopulate.newQRItem) { + return null; + } + return { linkId: qItem.linkId, text: qItem.text, @@ -181,13 +185,17 @@ function constructSingleItem( return qrItem ?? null; } - if (qrItem) { + if (qrItem && itemToRepopulate.newQRItem) { return { ...qrItem, answer: itemToRepopulate.newQRItem.answer }; } + if (!itemToRepopulate.newQRItem) { + return null; + } + return { linkId: qItem.linkId, text: qItem.text, @@ -202,7 +210,7 @@ function constructGridGroup( ) { const itemToRepopulate = checkedItemsToRepopulate[qItem.linkId]; - if (!itemToRepopulate) { + if (!itemToRepopulate || !itemToRepopulate.newQRItem) { return qrItem; } @@ -253,7 +261,7 @@ function constructRepeatGroup( ): QuestionnaireResponseItem[] { const itemToRepopulate = checkedItemsToRepopulate[qItem.linkId]; - if (!itemToRepopulate) { + if (!itemToRepopulate || !itemToRepopulate.newQRItems) { return qrItems; } diff --git a/packages/smart-forms-renderer/src/utils/repopulateItems.ts b/packages/smart-forms-renderer/src/utils/repopulateItems.ts index 5f019abae..aad2c40a6 100644 --- a/packages/smart-forms-renderer/src/utils/repopulateItems.ts +++ b/packages/smart-forms-renderer/src/utils/repopulateItems.ts @@ -23,12 +23,17 @@ import type { } from 'fhir/r4'; import type { Tabs } from '../interfaces/tab.interface'; import _isEqual from 'lodash/isEqual'; +import _intersection from 'lodash/intersection'; +import _difference from 'lodash/difference'; import { containsTabs, isTabContainer } from './tabs'; import { getShortText, isSpecificItemControl } from './itemControl'; import { getQrItemsIndex, mapQItemsIndex } from './mapItem'; import type { EnableWhenExpressions, EnableWhenItems } from '../interfaces/enableWhen.interface'; import { isHiddenByEnableWhen } from './qItem'; import { questionnaireResponseStore, questionnaireStore } from '../stores'; +import cloneDeep from 'lodash.clonedeep'; +import { createQuestionnaireResponseItemMap } from './questionnaireResponseStoreUtils/updatableResponseItems'; +import { getQuestionnaireItem, getSectionHeading } from './misc'; /** * ItemToRepopulate interface @@ -47,11 +52,11 @@ export interface ItemToRepopulate { heading: string | null; // for non-repeat groups - newQRItem: QuestionnaireResponseItem; + newQRItem?: QuestionnaireResponseItem; oldQRItem?: QuestionnaireResponseItem; // for repeat groups - newQRItems: QuestionnaireResponseItem[]; + newQRItems?: QuestionnaireResponseItem[]; oldQRItems?: QuestionnaireResponseItem[]; } @@ -74,11 +79,14 @@ export function generateItemsToRepopulate(populatedResponse: QuestionnaireRespon const sourceQuestionnaire = questionnaireStore.getState().sourceQuestionnaire; const tabs = questionnaireStore.getState().tabs; const updatableResponse = questionnaireResponseStore.getState().updatableResponse; + const updatableResponseItems = questionnaireResponseStore.getState().updatableResponseItems; const enableWhenIsActivated = questionnaireStore.getState().enableWhenIsActivated; const enableWhenItems = questionnaireStore.getState().enableWhenItems; const enableWhenExpressions = questionnaireStore.getState().enableWhenExpressions; + const initialExpressions = questionnaireStore.getState().initialExpressions; - return getItemsToRepopulate({ + // This function is able to capture additions, however it is not able to capture deletions + const itemsToRepopulate = getItemsToRepopulate({ sourceQuestionnaire, tabs, populatedResponse, @@ -87,6 +95,29 @@ export function generateItemsToRepopulate(populatedResponse: QuestionnaireRespon enableWhenItems, enableWhenExpressions }); + + // Get linkIds that are different between current QRItems and populated QRItems + // Doesn't work with repeat groups, but at the same time I'm not sure if it's needed, given you can't delete completely the first repeat group + const populatedResponseItemMap = createQuestionnaireResponseItemMap(populatedResponse); + const diffLinkIds = _difference( + Object.keys(updatableResponseItems), + Object.keys(populatedResponseItemMap) + ); + const diffLinkIdsWithInitialExpressions = _intersection( + Object.keys(initialExpressions), + diffLinkIds + ); + for (const linkId of diffLinkIdsWithInitialExpressions) { + if (linkId in updatableResponseItems) { + itemsToRepopulate[linkId] = { + qItem: getQuestionnaireItem(sourceQuestionnaire, linkId), + heading: getSectionHeading(sourceQuestionnaire, linkId, tabs), + oldQRItem: updatableResponseItems[linkId][0] + }; + } + } + + return itemsToRepopulate; } export function getItemsToRepopulate( @@ -283,6 +314,17 @@ function getSingleItemToRepopulate( heading: string | null, itemsToRepopulate: Record ) { + if (qItem.linkId === 'encounter-reason') { + console.log( + cloneDeep({ + qItem: qItem, + heading: heading, + newQRItem: qrItem, + newQRItems: [] + }) + ); + } + itemsToRepopulate[qItem.linkId] = { qItem: qItem, heading: heading,