diff --git a/packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/removeEmptyAnswers.ts b/packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/removeEmptyAnswers.ts index ac7900a1..5f67900c 100644 --- a/packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/removeEmptyAnswers.ts +++ b/packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/removeEmptyAnswers.ts @@ -37,25 +37,36 @@ export function removeEmptyAnswersFromResponse( return questionnaireResponse; } + const newQuestionnaireResponse: QuestionnaireResponse = { ...questionnaireResponse, item: [] }; for (const [i, topLevelQRItem] of topLevelQRItems.entries()) { const qItem = topLevelQItems[i]; if (!qItem) { continue; } + // If QR item don't have either item.item and item.answer, continue + if (!qrItemHasItemsOrAnswer(topLevelQRItem)) { + continue; + } + const newTopLevelQRItem = removeEmptyAnswersFromItemRecursive(qItem, topLevelQRItem); - if (newTopLevelQRItem && questionnaireResponse.item) { - questionnaireResponse.item[i] = { ...newTopLevelQRItem }; + if (newTopLevelQRItem && newQuestionnaireResponse.item) { + newQuestionnaireResponse.item.push(newTopLevelQRItem); } } - return questionnaireResponse; + return newQuestionnaireResponse; } function removeEmptyAnswersFromItemRecursive( qItem: QuestionnaireItem, qrItem: QuestionnaireResponseItem ): QuestionnaireResponseItem | null { + // If QR item don't have either item.item and item.answer, return null + if (!qrItemHasItemsOrAnswer(qrItem)) { + return null; + } + const qItems = qItem.item; const qrItems = qrItem.item; @@ -120,3 +131,12 @@ function answerIsEmpty(qrItem: QuestionnaireResponseItem) { return false; } + +/** + * Check if a QuestionnaireResponseItem has either an item or an answer property. + * + * @author Sean Fong + */ +function qrItemHasItemsOrAnswer(qrItem: QuestionnaireResponseItem): boolean { + return (!!qrItem.item && qrItem.item.length > 0) || (!!qrItem.answer && qrItem.answer.length > 0); +} diff --git a/packages/smart-forms-renderer/src/utils/calculatedExpression.ts b/packages/smart-forms-renderer/src/utils/calculatedExpression.ts index ed7722f8..bc2a5ceb 100644 --- a/packages/smart-forms-renderer/src/utils/calculatedExpression.ts +++ b/packages/smart-forms-renderer/src/utils/calculatedExpression.ts @@ -34,6 +34,7 @@ import { getQrItemsIndex, mapQItemsIndex } from './mapItem'; import { updateQrItemsInGroup } from './qrItem'; import cloneDeep from 'lodash.clonedeep'; import dayjs from 'dayjs'; +import { qrItemHasItemsOrAnswer } from './manageForm'; interface EvaluateInitialCalculatedExpressionsParams { initialResponse: QuestionnaireResponse; @@ -186,9 +187,16 @@ export function initialiseCalculatedExpressionValues( } // Populate calculated expression values into QR + const qItemsIndexMap = mapQItemsIndex(questionnaire); + const topLevelQRItemsByIndex = getQrItemsIndex( + questionnaire.item, + populatedResponse.item, + qItemsIndexMap + ); + const topLevelQrItems: QuestionnaireResponseItem[] = []; for (const [index, topLevelQItem] of questionnaire.item.entries()) { - const populatedTopLevelQrItem = populatedResponse.item[index] ?? { + const topLevelQRItemOrItems = topLevelQRItemsByIndex[index] ?? { linkId: topLevelQItem.linkId, text: topLevelQItem.text, item: [] @@ -196,7 +204,7 @@ export function initialiseCalculatedExpressionValues( const updatedTopLevelQRItem = initialiseItemCalculatedExpressionValueRecursive( topLevelQItem, - populatedTopLevelQrItem, + topLevelQRItemOrItems, calculatedExpressionsWithValues ); @@ -207,7 +215,7 @@ export function initialiseCalculatedExpressionValues( continue; } - if (updatedTopLevelQRItem) { + if (updatedTopLevelQRItem && qrItemHasItemsOrAnswer(updatedTopLevelQRItem)) { topLevelQrItems.push(updatedTopLevelQRItem); } } @@ -217,48 +225,44 @@ export function initialiseCalculatedExpressionValues( function initialiseItemCalculatedExpressionValueRecursive( qItem: QuestionnaireItem, - qrItem: QuestionnaireResponseItem | undefined, + qrItemOrItems: QuestionnaireResponseItem | QuestionnaireResponseItem[] | null, calculatedExpressions: Record -): QuestionnaireResponseItem[] | QuestionnaireResponseItem | null { +): QuestionnaireResponseItem | QuestionnaireResponseItem[] | null { + // For repeat groups + const hasMultipleAnswers = Array.isArray(qrItemOrItems); + if (hasMultipleAnswers) { + return qrItemOrItems; + } + + const qrItem = qrItemOrItems; const childQItems = qItem.item; if (childQItems && childQItems.length > 0) { - // iterate through items of item recursively const childQrItems = qrItem?.item ?? []; - // const updatedChildQrItems: QuestionnaireResponseItem[] = []; - - // FIXME Not implemented for repeat groups - if (qItem.type === 'group' && qItem.repeats) { - return qrItem ?? null; - } const indexMap = mapQItemsIndex(qItem); const qrItemsByIndex = getQrItemsIndex(childQItems, childQrItems, indexMap); // Otherwise loop through qItem as usual for (const [index, childQItem] of childQItems.entries()) { - const childQrItem = qrItemsByIndex[index]; - - // FIXME Not implemented for repeat groups - if (Array.isArray(childQrItem)) { - continue; - } + const childQRItemOrItems = qrItemsByIndex[index]; - const newQrItem = initialiseItemCalculatedExpressionValueRecursive( + const updatedChildQRItemOrItems = initialiseItemCalculatedExpressionValueRecursive( childQItem, - childQrItem, + childQRItemOrItems ?? null, calculatedExpressions ); // FIXME Not implemented for repeat groups - if (Array.isArray(newQrItem)) { + if (Array.isArray(updatedChildQRItemOrItems)) { continue; } - if (newQrItem) { + const updatedChildQRItem = updatedChildQRItemOrItems; + if (updatedChildQRItem) { updateQrItemsInGroup( - newQrItem, + updatedChildQRItem, null, - qrItem ?? { linkId: qItem.linkId, text: qItem.text, item: [] }, + updatedChildQRItem ?? { linkId: qItem.linkId, text: qItem.text, item: [] }, indexMap ); } @@ -287,7 +291,7 @@ function getCalculatedExpressionAnswer( function constructGroupItem( qItem: QuestionnaireItem, - qrItem: QuestionnaireResponseItem | undefined, + qrItem: QuestionnaireResponseItem | null, calculatedExpressions: Record ): QuestionnaireResponseItem | null { const calculatedExpressionAnswer = getCalculatedExpressionAnswer(qItem, calculatedExpressions); @@ -317,7 +321,7 @@ function constructGroupItem( function constructSingleItem( qItem: QuestionnaireItem, - qrItem: QuestionnaireResponseItem | undefined, + qrItem: QuestionnaireResponseItem | null, calculatedExpressions: Record ): QuestionnaireResponseItem | null { const calculatedExpressionAnswer = getCalculatedExpressionAnswer(qItem, calculatedExpressions); diff --git a/packages/smart-forms-renderer/src/utils/manageForm.ts b/packages/smart-forms-renderer/src/utils/manageForm.ts index 9fd0b927..f799e0e4 100644 --- a/packages/smart-forms-renderer/src/utils/manageForm.ts +++ b/packages/smart-forms-renderer/src/utils/manageForm.ts @@ -1,4 +1,4 @@ -import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4'; +import type { Questionnaire, QuestionnaireResponse, QuestionnaireResponseItem } from 'fhir/r4'; import { questionnaireResponseStore, questionnaireStore, @@ -120,3 +120,12 @@ export function removeEmptyAnswersFromResponse( enableWhenExpressions }); } + +/** + * Check if a QuestionnaireResponseItem has either an item or an answer property. + * + * @author Sean Fong + */ +export function qrItemHasItemsOrAnswer(qrItem: QuestionnaireResponseItem): boolean { + return (!!qrItem.item && qrItem.item.length > 0) || (!!qrItem.answer && qrItem.answer.length > 0); +} diff --git a/packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts b/packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts index a8a29d2a..1422f212 100644 --- a/packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts +++ b/packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts @@ -24,6 +24,7 @@ import type { import type { EnableWhenExpressions, EnableWhenItems } from '../interfaces/enableWhen.interface'; import { isHiddenByEnableWhen } from './qItem'; import cloneDeep from 'lodash.clonedeep'; +import { qrItemHasItemsOrAnswer } from './manageForm'; interface removeEmptyAnswersParams { questionnaire: Questionnaire; @@ -60,21 +61,31 @@ export function removeEmptyAnswers(params: removeEmptyAnswersParams): Questionna return updatedQuestionnaireResponse; } - topLevelQRItems.forEach((qrItem, i) => { + const newQuestionnaireResponse: QuestionnaireResponse = { ...questionnaireResponse, item: [] }; + for (const [i, topLevelQRItem] of topLevelQRItems.entries()) { const qItem = topLevelQItems[i]; + if (!qItem) { + continue; + } + + // If QR item don't have either item.item and item.answer, continue + if (!qrItemHasItemsOrAnswer(topLevelQRItem)) { + continue; + } + const newTopLevelQRItem = removeEmptyAnswersFromItemRecursive({ qItem, - qrItem, + qrItem: topLevelQRItem, enableWhenIsActivated, enableWhenItems, enableWhenExpressions }); - if (newTopLevelQRItem && questionnaireResponse.item) { - questionnaireResponse.item[i] = { ...newTopLevelQRItem }; + if (newTopLevelQRItem && newQuestionnaireResponse.item) { + newQuestionnaireResponse.item.push(newTopLevelQRItem); } - }); + } - return questionnaireResponse; + return newQuestionnaireResponse; } interface removeEmptyAnswersFromItemRecursiveParams { @@ -90,6 +101,11 @@ function removeEmptyAnswersFromItemRecursive( ): QuestionnaireResponseItem | null { const { qItem, qrItem, enableWhenIsActivated, enableWhenItems, enableWhenExpressions } = params; + // If QR item don't have either item.item and item.answer, return null + if (!qrItemHasItemsOrAnswer(qrItem)) { + return null; + } + const qItems = qItem.item; const qrItems = qrItem.item; diff --git a/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts b/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts index 32b80bda..936d7105 100644 --- a/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts +++ b/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts @@ -8,6 +8,7 @@ import type { ItemToRepopulate } from './repopulateItems'; import { getQrItemsIndex, mapQItemsIndex } from './mapItem'; import { isSpecificItemControl } from './itemControl'; import { questionnaireResponseStore, questionnaireStore } from '../stores'; +import { qrItemHasItemsOrAnswer } from './manageForm'; /** * Re-populate checked items in the re-population dialog into the current QuestionnaireResponse @@ -67,7 +68,7 @@ export function repopulateItemsIntoResponse( continue; } - if (updatedTopLevelQRItem) { + if (updatedTopLevelQRItem && qrItemHasItemsOrAnswer(updatedTopLevelQRItem)) { topLevelQrItems.push(updatedTopLevelQRItem); } } @@ -79,7 +80,7 @@ function repopulateItemRecursive( qItem: QuestionnaireItem, qrItemOrItems: QuestionnaireResponseItem | QuestionnaireResponseItem[] | null, checkedItemsToRepopulate: Record -): QuestionnaireResponseItem[] | QuestionnaireResponseItem | null { +): QuestionnaireResponseItem | QuestionnaireResponseItem[] | null { // For repeat groups const hasMultipleAnswers = Array.isArray(qrItemOrItems); if (hasMultipleAnswers) {