diff --git a/apps/smart-forms-app/src/utils/qrItem.ts b/apps/smart-forms-app/src/utils/qrItem.ts deleted file mode 100644 index fc50f9c59..000000000 --- a/apps/smart-forms-app/src/utils/qrItem.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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. - */ -import type { QuestionnaireResponse } from 'fhir/r4'; - -export const emptyResponse: QuestionnaireResponse = { - resourceType: 'QuestionnaireResponse', - status: 'in-progress' -}; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/AttachmentItem/AttachmentItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/AttachmentItem/AttachmentItem.tsx index 3c4a8833c..8f24940b3 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/AttachmentItem/AttachmentItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/AttachmentItem/AttachmentItem.tsx @@ -54,9 +54,9 @@ function AttachmentItem(props: AttachmentItemProps) { const readOnly = useReadOnly(qItem, parentIsReadOnly); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueString = ''; - if (qrItem?.answer && qrItem?.answer[0].valueString) { + if (qrItem?.answer && qrItem?.answer[0]?.valueString) { valueString = qrItem.answer[0].valueString; } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/BooleanItem/BooleanItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/BooleanItem/BooleanItem.tsx index 2157273f3..9bfbbc737 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/BooleanItem/BooleanItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/BooleanItem/BooleanItem.tsx @@ -49,7 +49,7 @@ function BooleanItem(props: BooleanItemProps) { const readOnly = useReadOnly(qItem, parentIsReadOnly); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueBoolean: boolean | undefined = undefined; if (qrItem?.answer?.[0]?.valueBoolean !== undefined) { valueBoolean = qrItem.answer[0].valueBoolean; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.tsx index 60c98cc0c..5554c29a1 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.tsx @@ -49,12 +49,12 @@ function ChoiceAutocompleteItem(props: ChoiceAutocompleteItemProps) { const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId(); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrChoice = qrItem ?? createEmptyQrItem(qItem, answerKey); let valueCoding: Coding | undefined; if (qrChoice.answer) { - valueCoding = qrChoice.answer[0].valueCoding; + valueCoding = qrChoice.answer[0]?.valueCoding; } const readOnly = useReadOnly(qItem, parentIsReadOnly); diff --git a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx index 06b8c5727..ab7d7f2b6 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx @@ -55,7 +55,7 @@ function ChoiceCheckboxAnswerOptionItem(props: ChoiceCheckboxAnswerOptionItemPro const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId(); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrChoiceCheckbox = qrItem ?? createEmptyQrItem(qItem, answerKey); const answers = qrChoiceCheckbox.answer ?? []; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx index 9c4570d43..2a99149ac 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx @@ -57,7 +57,7 @@ function ChoiceCheckboxAnswerValueSetItem(props: ChoiceCheckboxAnswerValueSetIte const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId(); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrChoiceCheckbox = qrItem ?? createEmptyQrItem(qItem, answerKey); const answers = qrChoiceCheckbox.answer ?? []; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx index b5113263a..f7d23ddce 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx @@ -52,7 +52,7 @@ function ChoiceRadioAnswerOptionItem(props: ChoiceRadioAnswerOptionItemProps) { const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId(); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrChoice = qrItem ?? createEmptyQrItem(qItem, answerKey); const valueChoice = getQrChoiceValue(qrChoice); diff --git a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx index 09bafd78a..c4946897d 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx @@ -48,12 +48,12 @@ function ChoiceRadioAnswerValueSetItem(props: ChoiceRadioAnswerValueSetItemProps const readOnly = useReadOnly(qItem, parentIsReadOnly); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrChoiceRadio = qrItem ?? createEmptyQrItem(qItem, answerKey); let valueRadio: string | null = null; if (qrChoiceRadio.answer) { - valueRadio = qrChoiceRadio.answer[0].valueCoding?.code ?? null; + valueRadio = qrChoiceRadio.answer[0]?.valueCoding?.code ?? null; } // Get codings/options from valueSet diff --git a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx index d697d9203..a530c6643 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx @@ -53,7 +53,7 @@ function ChoiceSelectAnswerOptionItem(props: ChoiceSelectAnswerOptionItemProps) const readOnly = useReadOnly(qItem, parentIsReadOnly); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrChoice = qrItem ?? createEmptyQrItem(qItem, answerKey); const valueChoice = getQrChoiceValue(qrChoice); diff --git a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx index 7208200a0..90bb6d86b 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx @@ -51,12 +51,12 @@ function ChoiceSelectAnswerValueSetItem(props: ChoiceSelectAnswerValueSetItemPro const readOnly = useReadOnly(qItem, parentIsReadOnly); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrChoiceSelect = qrItem ?? createEmptyQrItem(qItem, answerKey); let valueCoding: Coding | null = null; if (qrChoiceSelect.answer) { - valueCoding = qrChoiceSelect.answer[0].valueCoding ?? null; + valueCoding = qrChoiceSelect.answer[0]?.valueCoding ?? null; } // Get codings/options from valueSet @@ -74,7 +74,7 @@ function ChoiceSelectAnswerValueSetItem(props: ChoiceSelectAnswerValueSetItemPro // but will fail if answer provided is not within options useEffect( () => { - if (qrChoiceSelect.answer && qrChoiceSelect.answer[0].valueString) { + if (qrChoiceSelect.answer && qrChoiceSelect.answer[0]?.valueString) { onQrItemChange(createEmptyQrItem(qItem, answerKey)); } }, diff --git a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateItem/CustomDateItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateItem/CustomDateItem.tsx index ac714722b..426dfe51c 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateItem/CustomDateItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateItem/CustomDateItem.tsx @@ -56,14 +56,14 @@ function CustomDateItem(props: CustomDateItemProps) { const { displayPrompt, entryFormat } = useRenderingExtensions(qItem); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrDate = qrItem ?? createEmptyQrItem(qItem, answerKey); let valueDate: string = ''; if (qrDate.answer) { - if (qrDate.answer[0].valueDate) { + if (qrDate.answer[0]?.valueDate) { valueDate = qrDate.answer[0].valueDate; - } else if (qrDate.answer[0].valueDateTime) { + } else if (qrDate.answer[0]?.valueDateTime) { valueDate = qrDate.answer[0].valueDateTime; } } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateTimeItem/CustomDateTimeItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateTimeItem/CustomDateTimeItem.tsx index 3d00f6f8b..dffc98f77 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateTimeItem/CustomDateTimeItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateTimeItem/CustomDateTimeItem.tsx @@ -63,22 +63,22 @@ function CustomDateTimeItem(props: CustomDateTimeItemProps) { const { displayPrompt, entryFormat } = useRenderingExtensions(qItem); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrDateTime = qrItem ?? createEmptyQrItem(qItem, answerKey); let valueDate: string = ''; let dateTimeDayJs: Dayjs | null = null; if (qrDateTime.answer) { let tempDateTime = ''; - if (qrDateTime.answer[0].valueDate) { - tempDateTime = qrDateTime.answer[0].valueDate; - } else if (qrDateTime.answer[0].valueDateTime) { - tempDateTime = qrDateTime.answer[0].valueDateTime; + if (qrDateTime.answer[0]?.valueDate) { + tempDateTime = qrDateTime.answer[0]?.valueDate; + } else if (qrDateTime.answer[0]?.valueDateTime) { + tempDateTime = qrDateTime.answer[0]?.valueDateTime; } // split date and time at "T", 2015-02-07T13:28:17-05:00 if (tempDateTime.includes('T')) { - valueDate = tempDateTime.split('T')[0]; + valueDate = tempDateTime.split('T')[0] ?? ''; dateTimeDayJs = dayjs(tempDateTime); } else { valueDate = tempDateTime; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/utils/parseDate.ts b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/utils/parseDate.ts index 7cb836432..5415b22fe 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/utils/parseDate.ts +++ b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/utils/parseDate.ts @@ -48,6 +48,10 @@ export function validateDateInput(input: string) { return false; } + if (!matches[1] || !matches[2]) { + return false; + } + // Handle MM/YYYY format if (matches.length === 2) { return validateTwoMatches(matches[0], matches[1]); diff --git a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/utils/parseTime.ts b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/utils/parseTime.ts index a6ff16efb..ab2357321 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/utils/parseTime.ts +++ b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/utils/parseTime.ts @@ -33,7 +33,7 @@ export function getTimeSegments(timeInput: string): { return { hourSegment: null, minuteSegment: null }; } - return { hourSegment: timeInputSegments[0], minuteSegment: timeInputSegments[1] }; + return { hourSegment: timeInputSegments[0] ?? null, minuteSegment: timeInputSegments[1] ?? null }; } export function validateTimeInput( @@ -102,7 +102,7 @@ export function parseDateTimeToDisplayTime(dateTime: Dayjs | null): { function convertTo12HourFormat(timeInput: string) { const timeSegments = timeInput.split(':'); - const hour = parseInt(timeSegments[0], 10); + const hour = parseInt(timeSegments[0] ?? '', 10) || 0; if (hour >= 12) { timeSegments[0] = `${hour - 12}`; } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/DecimalItem/DecimalItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/DecimalItem/DecimalItem.tsx index cfec5e756..9147bf57d 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/DecimalItem/DecimalItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/DecimalItem/DecimalItem.tsx @@ -61,15 +61,15 @@ function DecimalItem(props: DecimalItemProps) { const { displayUnit, displayPrompt, entryFormat } = useRenderingExtensions(qItem); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueDecimal = 0.0; let initialInput = ''; if (qrItem?.answer) { - if (qrItem?.answer[0].valueDecimal) { + if (qrItem?.answer[0]?.valueDecimal) { valueDecimal = qrItem.answer[0].valueDecimal; } - if (qrItem?.answer[0].valueInteger) { + if (qrItem?.answer[0]?.valueInteger) { valueDecimal = qrItem.answer[0].valueInteger; } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/GridGroup/GridGroup.tsx b/packages/smart-forms-renderer/src/components/FormComponents/GridGroup/GridGroup.tsx index f257b9c02..f863f3bde 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/GridGroup/GridGroup.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/GridGroup/GridGroup.tsx @@ -63,7 +63,7 @@ function GridGroup(props: GridGroupProps) { const qItemsIndexMap = useMemo(() => mapQItemsIndex(qItem), [qItem]); const columnLabels: string[] = useMemo( - () => qRowItems?.[0].item?.map((firstItem) => firstItem.text ?? ' ') ?? [], + () => qRowItems?.[0]?.item?.map((firstItem) => firstItem.text ?? ' ') ?? [], [qRowItems] ); diff --git a/packages/smart-forms-renderer/src/components/FormComponents/IntegerItem/IntegerItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/IntegerItem/IntegerItem.tsx index cb0a17fbd..423e89384 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/IntegerItem/IntegerItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/IntegerItem/IntegerItem.tsx @@ -55,15 +55,15 @@ function IntegerItem(props: IntegerItemProps) { const { displayUnit, displayPrompt, entryFormat } = useRenderingExtensions(qItem); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueInteger = 0; let initialInput = ''; if (qrItem?.answer) { - if (qrItem?.answer[0].valueInteger) { + if (qrItem?.answer[0]?.valueInteger) { valueInteger = qrItem.answer[0].valueInteger; } - if (qrItem?.answer[0].valueDecimal) { + if (qrItem?.answer[0]?.valueDecimal) { valueInteger = Math.round(qrItem.answer[0].valueDecimal); } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.tsx index 1bb11ac31..1919b8928 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.tsx @@ -51,14 +51,14 @@ function OpenChoiceAutocompleteItem(props: OpenChoiceAutocompleteItemProps) { const readOnly = useReadOnly(qItem, parentIsReadOnly); - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrOpenChoice = qrItem ?? createEmptyQrItem(qItem, answerKey); // Init input value let valueAutocomplete: Coding | string | undefined; if (qrOpenChoice.answer) { const answer = qrOpenChoice.answer[0]; - valueAutocomplete = answer.valueCoding ? answer.valueCoding : answer.valueString; + valueAutocomplete = answer?.valueCoding ? answer.valueCoding : answer?.valueString; } if (!valueAutocomplete) { diff --git a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.tsx index 351abb05c..43590f89c 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.tsx @@ -60,7 +60,7 @@ function OpenChoiceCheckboxAnswerOptionItem(props: OpenChoiceCheckboxAnswerOptio const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId(); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrOpenChoiceCheckbox = qrItem ?? createEmptyQrItem(qItem, answerKey); const answers = qrOpenChoiceCheckbox.answer ?? []; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerValueSetItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerValueSetItem.tsx index b39c728e9..f5945af00 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerValueSetItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerValueSetItem.tsx @@ -61,7 +61,7 @@ function OpenChoiceCheckboxAnswerValueSetItem(props: OpenChoiceCheckboxAnswerVal const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId(); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrOpenChoiceCheckbox = qrItem ?? createEmptyQrItem(qItem, answerKey); const answers = qrOpenChoiceCheckbox.answer ?? []; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.tsx index 43a6aeb34..02b3d8209 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.tsx @@ -46,7 +46,7 @@ function OpenChoiceRadioAnswerOptionItem(props: OpenChoiceRadioAnswerOptionItemP const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId(); // Init answers - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrOpenChoiceRadio = qrItem ?? createEmptyQrItem(qItem, answerKey); let valueRadio: string | null = getQrChoiceValue(qrOpenChoiceRadio, true); const answers = qrOpenChoiceRadio.answer ?? []; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetItem.tsx index e5f57b29a..b51d7fb12 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetItem.tsx @@ -51,7 +51,7 @@ function OpenChoiceRadioAnswerValueSetItem(props: OpenChoiceRadioAnswerValueSetI const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId(); // Init answers - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrOpenChoiceRadio = qrItem ?? createEmptyQrItem(qItem, answerKey); let valueRadio: string | null = getQrChoiceValue(qrOpenChoiceRadio, true); const answers = qrOpenChoiceRadio.answer ?? []; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerOptionItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerOptionItem.tsx index ce2f4ddfe..ecc7910d5 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerOptionItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerOptionItem.tsx @@ -51,7 +51,7 @@ function OpenChoiceSelectAnswerOptionItem(props: OpenChoiceSelectAnswerOptionIte const readOnly = useReadOnly(qItem, parentIsReadOnly); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const answerOptions = qItem.answerOption; if (!answerOptions) return null; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.tsx index 2e0e30d0a..4dc408b0a 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.tsx @@ -48,12 +48,12 @@ function OpenChoiceSelectAnswerValueSetItem(props: OpenChoiceSelectAnswerValueSe const readOnly = useReadOnly(qItem, parentIsReadOnly); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; const qrOpenChoice = qrItem ?? createEmptyQrItem(qItem, answerKey); let valueSelect: Coding | null = null; if (qrOpenChoice['answer']) { - valueSelect = qrOpenChoice['answer'][0].valueCoding ?? null; + valueSelect = qrOpenChoice['answer'][0]?.valueCoding ?? null; } // Get codings/options from valueSet diff --git a/packages/smart-forms-renderer/src/components/FormComponents/QuantityItem/QuantityItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/QuantityItem/QuantityItem.tsx index 867fb1852..24b88bb1e 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/QuantityItem/QuantityItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/QuantityItem/QuantityItem.tsx @@ -62,14 +62,14 @@ function QuantityItem(props: QuantityItemProps) { ); // Init inputs - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueQuantity: Quantity = {}; let initialValueInput = ''; let initialComparatorInput: Quantity['comparator'] | null = null; let initialUnitInput: QuestionnaireItemAnswerOption | null = quantityUnit ?? unitOptions?.at(0) ?? null; if (qrItem?.answer) { - if (qrItem?.answer[0].valueQuantity) { + if (qrItem?.answer[0]?.valueQuantity) { valueQuantity = qrItem.answer[0].valueQuantity; } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx b/packages/smart-forms-renderer/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx index 8f9c6358b..c58f86533 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx @@ -65,8 +65,9 @@ function RepeatGroup(props: RepeatGroupProps) { function handleAnswerChange(newQrItem: QuestionnaireResponseItem, index: number) { const updatedRepeatGroups = [...repeatGroups]; - if (newQrItem.item) { - updatedRepeatGroups[index].qrItem = { + const updatedRepeatGroup = updatedRepeatGroups[index]; + if (newQrItem.item && updatedRepeatGroup) { + updatedRepeatGroup.qrItem = { linkId: newQrItem.linkId, text: newQrItem.text, item: newQrItem.item diff --git a/packages/smart-forms-renderer/src/components/FormComponents/RepeatItem/RepeatItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/RepeatItem/RepeatItem.tsx index 2ac434c6a..3d01cc30b 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/RepeatItem/RepeatItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/RepeatItem/RepeatItem.tsx @@ -61,7 +61,7 @@ function RepeatItem(props: RepeatItemProps) { // Event Handlers function handleAnswerChange(newQrItem: QuestionnaireResponseItem, index: number) { const updatedRepeatAnswers = [...repeatAnswers]; - updatedRepeatAnswers[index] = newQrItem.answer ? newQrItem.answer[0] : null; + updatedRepeatAnswers[index] = newQrItem.answer ? newQrItem.answer[0] ?? null : null; onQrItemChange({ ...createEmptyQrItem(qItem, undefined), diff --git a/packages/smart-forms-renderer/src/components/FormComponents/SliderItem/SliderItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/SliderItem/SliderItem.tsx index 4443a546a..a7fe2fdbb 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/SliderItem/SliderItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/SliderItem/SliderItem.tsx @@ -52,13 +52,13 @@ function SliderItem(props: SliderItemProps) { const isInteracted = !!qrItem?.answer; // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueInteger = 0; if (qrItem?.answer) { - if (qrItem?.answer[0].valueInteger) { + if (qrItem?.answer[0]?.valueInteger) { valueInteger = qrItem.answer[0].valueInteger; } - if (qrItem?.answer[0].valueDecimal) { + if (qrItem?.answer[0]?.valueDecimal) { valueInteger = Math.round(qrItem.answer[0].valueDecimal); } } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/StringItem/StringItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/StringItem/StringItem.tsx index 39d693baf..2797801aa 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/StringItem/StringItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/StringItem/StringItem.tsx @@ -53,9 +53,9 @@ function StringItem(props: StringItemProps) { const { displayUnit, displayPrompt, entryFormat } = useRenderingExtensions(qItem); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueString = ''; - if (qrItem?.answer && qrItem?.answer[0].valueString) { + if (qrItem?.answer && qrItem?.answer[0]?.valueString) { valueString = qrItem.answer[0].valueString; } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/Tables/AddRowButton.tsx b/packages/smart-forms-renderer/src/components/FormComponents/Tables/AddRowButton.tsx index f5298f909..475ea390b 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/Tables/AddRowButton.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/Tables/AddRowButton.tsx @@ -30,7 +30,7 @@ interface AddItemButtonProps { function AddRowButton(props: AddItemButtonProps) { const { repeatGroups, readOnly, onAddItem } = props; - const isDisabled = repeatGroups[repeatGroups.length - 1].qrItem === null || readOnly; + const isDisabled = repeatGroups[repeatGroups.length - 1]?.qrItem === null || readOnly; return ( diff --git a/packages/smart-forms-renderer/src/components/FormComponents/Tables/GroupTable.tsx b/packages/smart-forms-renderer/src/components/FormComponents/Tables/GroupTable.tsx index 5b1d64eda..3d18a774b 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/Tables/GroupTable.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/Tables/GroupTable.tsx @@ -82,8 +82,9 @@ function GroupTable(props: GroupTableProps) { function handleRowChange(newQrRow: QuestionnaireResponseItem, index: number) { const updatedTableRows = [...tableRows]; - if (newQrRow.item) { - updatedTableRows[index].qrItem = { + const updatedTableRow = updatedTableRows[index]; + if (newQrRow.item && updatedTableRow) { + updatedTableRow.qrItem = { linkId: newQrRow.linkId, text: newQrRow.text, item: newQrRow.item @@ -101,7 +102,7 @@ function GroupTable(props: GroupTableProps) { const updatedTableRows = [...tableRows]; const rowToRemove = updatedTableRows[index]; - const updatedSelectedIds = selectedIds.filter((id) => id !== rowToRemove.nanoId); + const updatedSelectedIds = selectedIds.filter((id) => id !== rowToRemove?.nanoId); updatedTableRows.splice(index, 1); diff --git a/packages/smart-forms-renderer/src/components/FormComponents/TextItem/TextItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/TextItem/TextItem.tsx index 3ef5e9d8b..dd4cd8dea 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/TextItem/TextItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/TextItem/TextItem.tsx @@ -52,9 +52,9 @@ function TextItem(props: TextItemProps) { const { displayUnit, displayPrompt, entryFormat } = useRenderingExtensions(qItem); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueText = ''; - if (qrItem?.answer && qrItem?.answer[0].valueString) { + if (qrItem?.answer && qrItem?.answer[0]?.valueString) { valueText = qrItem.answer[0].valueString; } diff --git a/packages/smart-forms-renderer/src/components/FormComponents/TimeItem/TimeItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/TimeItem/TimeItem.tsx index 11ea7874b..4fc043882 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/TimeItem/TimeItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/TimeItem/TimeItem.tsx @@ -51,9 +51,9 @@ function TimeItem(props: TimeItemProps) { const { displayPrompt, entryFormat } = useRenderingExtensions(qItem); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let timeString: string | null = null; - if (qrItem?.answer && qrItem?.answer[0].valueTime) { + if (qrItem?.answer && qrItem?.answer[0]?.valueTime) { timeString = qrItem.answer[0].valueTime; } const timeDayJs = timeString ? dayjs(timeString) : null; diff --git a/packages/smart-forms-renderer/src/components/FormComponents/UrlItem/UrlItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/UrlItem/UrlItem.tsx index 6f03ee8fe..fd0096900 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/UrlItem/UrlItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/UrlItem/UrlItem.tsx @@ -52,9 +52,9 @@ function UrlItem(props: UrlItemProps) { const { displayUnit, displayPrompt, entryFormat } = useRenderingExtensions(qItem); // Init input value - const answerKey = qrItem?.answer?.[0].id; + const answerKey = qrItem?.answer?.[0]?.id; let valueUri = ''; - if (qrItem?.answer && qrItem?.answer[0].valueUri) { + if (qrItem?.answer && qrItem?.answer[0]?.valueUri) { valueUri = qrItem.answer[0].valueUri; } diff --git a/packages/smart-forms-renderer/src/components/Renderer/FormBodyPage.tsx b/packages/smart-forms-renderer/src/components/Renderer/FormBodyPage.tsx index e9189907e..398ee9bd4 100644 --- a/packages/smart-forms-renderer/src/components/Renderer/FormBodyPage.tsx +++ b/packages/smart-forms-renderer/src/components/Renderer/FormBodyPage.tsx @@ -62,7 +62,7 @@ function FormBodyPage(props: FormBodyPageProps) { } const isRepeated = qItem.repeats ?? false; - const pageIsMarkedAsComplete = pages[qItem.linkId].isComplete ?? false; + const pageIsMarkedAsComplete = pages[qItem.linkId]?.isComplete ?? false; return ( {topLevelItems.map((qItem, i) => { - const contextDisplayItems = allContextDisplayItems[i]; + const contextDisplayItems = allContextDisplayItems[i] ?? []; const isTab = !!tabs[qItem.linkId]; if ( diff --git a/packages/smart-forms-renderer/src/hooks/useBooleanCalculatedExpression.ts b/packages/smart-forms-renderer/src/hooks/useBooleanCalculatedExpression.ts index 7b846a986..753db28a2 100644 --- a/packages/smart-forms-renderer/src/hooks/useBooleanCalculatedExpression.ts +++ b/packages/smart-forms-renderer/src/hooks/useBooleanCalculatedExpression.ts @@ -46,31 +46,30 @@ function useBooleanCalculatedExpression( (exp) => exp.from === 'item' ); - if (!calcExpression) { - return; - } + if (calcExpression) { + // only update if calculated value is different from current value + if ( + calcExpression.value !== booleanValue && + (typeof calcExpression.value === 'boolean' || calcExpression.value === null) + ) { + // update ui to show calculated value changes + setCalcExpUpdated(true); + const timeoutId = setTimeout(() => { + setCalcExpUpdated(false); + }, 500); - // only update if calculated value is different from current value - if ( - calcExpression.value !== booleanValue && - (typeof calcExpression.value === 'boolean' || calcExpression.value === null) - ) { - // update ui to show calculated value changes - setCalcExpUpdated(true); - const timeoutId = setTimeout(() => { - setCalcExpUpdated(false); - }, 500); + // calculatedExpression value is null + if (calcExpression.value === null) { + onChangeByCalcExpressionNull(); + return () => clearTimeout(timeoutId); + } - // calculatedExpression value is null - if (calcExpression.value === null) { - onChangeByCalcExpressionNull(); + // calculatedExpression value is boolean + onChangeByCalcExpressionBoolean(calcExpression.value); return () => clearTimeout(timeoutId); } - - // calculatedExpression value is boolean - onChangeByCalcExpressionBoolean(calcExpression.value); - return () => clearTimeout(timeoutId); } + return () => {}; }, // Only trigger this effect if calculatedExpression of item changes // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/smart-forms-renderer/src/hooks/useCodingCalculatedExpression.ts b/packages/smart-forms-renderer/src/hooks/useCodingCalculatedExpression.ts index 7f13aecf7..e34ce2811 100644 --- a/packages/smart-forms-renderer/src/hooks/useCodingCalculatedExpression.ts +++ b/packages/smart-forms-renderer/src/hooks/useCodingCalculatedExpression.ts @@ -47,47 +47,47 @@ function useCodingCalculatedExpression( (exp) => exp.from === 'item' ); - if (!calcExpression) { - return; - } - - // only update if calculated value is different from current value - if ( - calcExpression.value !== valueInString && - (typeof calcExpression.value === 'string' || - typeof calcExpression.value === 'number' || - typeof calcExpression.value === 'object' || - calcExpression.value === null) - ) { - // update ui to show calculated value changes - setCalcExpUpdated(true); - const timeoutId = setTimeout(() => { - setCalcExpUpdated(false); - }, 500); - - // calculatedExpression value is null - if (calcExpression.value === null) { - onChangeByCalcExpressionNull(); - return () => clearTimeout(timeoutId); - } - - // calculatedExpression value is object, check if it is a Coding object - if (typeof calcExpression.value === 'object' && objectIsCoding(calcExpression.value)) { - if (calcExpression.value.code) { - onChangeByCalcExpressionString(calcExpression.value.code); + if (calcExpression) { + // only update if calculated value is different from current value + if ( + calcExpression.value !== valueInString && + (typeof calcExpression.value === 'string' || + typeof calcExpression.value === 'number' || + typeof calcExpression.value === 'object' || + calcExpression.value === null) + ) { + // update ui to show calculated value changes + setCalcExpUpdated(true); + const timeoutId = setTimeout(() => { + setCalcExpUpdated(false); + }, 500); + + // calculatedExpression value is null + if (calcExpression.value === null) { + onChangeByCalcExpressionNull(); return () => clearTimeout(timeoutId); } - } - // calculatedExpression value is a string or number - const newValueString = - typeof calcExpression.value === 'string' - ? calcExpression.value - : calcExpression.value.toString(); + // calculatedExpression value is object, check if it is a Coding object + if (typeof calcExpression.value === 'object' && objectIsCoding(calcExpression.value)) { + if (calcExpression.value.code) { + onChangeByCalcExpressionString(calcExpression.value.code); + return () => clearTimeout(timeoutId); + } + } + + // calculatedExpression value is a string or number + const newValueString = + typeof calcExpression.value === 'string' + ? calcExpression.value + : calcExpression.value.toString(); - onChangeByCalcExpressionString(newValueString); - return () => clearTimeout(timeoutId); + onChangeByCalcExpressionString(newValueString); + return () => clearTimeout(timeoutId); + } } + + return () => {}; }, // Only trigger this effect if calculatedExpression of item changes // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/smart-forms-renderer/src/hooks/useDateValidation.tsx b/packages/smart-forms-renderer/src/hooks/useDateValidation.tsx index d55a5fde6..e469e1be5 100644 --- a/packages/smart-forms-renderer/src/hooks/useDateValidation.tsx +++ b/packages/smart-forms-renderer/src/hooks/useDateValidation.tsx @@ -40,6 +40,10 @@ function useDateValidation(input: string, parseFail: boolean = false): string | } const matches = input.split('/'); + if (!matches[0] || !matches[1] || !matches[2]) { + return 'Input is an invalid date.'; + } + if (!validateThreeMatches(matches[0], matches[1], matches[2])) { return 'Input is an invalid date.'; } @@ -54,6 +58,9 @@ function useDateValidation(input: string, parseFail: boolean = false): string | } const matches = input.split('/'); + if (!matches[0] || !matches[1]) { + return 'Input is an invalid date.'; + } if (validateTwoMatches(matches[0], matches[1])) { return null; diff --git a/packages/smart-forms-renderer/src/hooks/useDecimalCalculatedExpression.ts b/packages/smart-forms-renderer/src/hooks/useDecimalCalculatedExpression.ts index e3b54e813..c370913d8 100644 --- a/packages/smart-forms-renderer/src/hooks/useDecimalCalculatedExpression.ts +++ b/packages/smart-forms-renderer/src/hooks/useDecimalCalculatedExpression.ts @@ -52,39 +52,39 @@ function useDecimalCalculatedExpression( (exp) => exp.from === 'item' ); - if (!calcExpression) { - return; - } + if (calcExpression) { + // only update if calculated value is different from current value + if ( + calcExpression.value !== inputValue && + (typeof calcExpression.value === 'number' || calcExpression.value === null) + ) { + const calcExpressionValue = + typeof calcExpression.value === 'number' && typeof precision === 'number' + ? parseFloat(calcExpression.value.toFixed(precision)) + : calcExpression.value; - // only update if calculated value is different from current value - if ( - calcExpression.value !== inputValue && - (typeof calcExpression.value === 'number' || calcExpression.value === null) - ) { - const calcExpressionValue = - typeof calcExpression.value === 'number' && typeof precision === 'number' - ? parseFloat(calcExpression.value.toFixed(precision)) - : calcExpression.value; + // only update if calculated value is different from current value + if (calcExpressionValue !== parseFloat(inputValue)) { + // update ui to show calculated value changes + setCalcExpUpdated(true); + const timeoutId = setTimeout(() => { + setCalcExpUpdated(false); + }, 500); - // only update if calculated value is different from current value - if (calcExpressionValue !== parseFloat(inputValue)) { - // update ui to show calculated value changes - setCalcExpUpdated(true); - const timeoutId = setTimeout(() => { - setCalcExpUpdated(false); - }, 500); + // calculatedExpression value is null + if (calcExpressionValue === null) { + onChangeByCalcExpressionNull(); + return () => clearTimeout(timeoutId); + } - // calculatedExpression value is null - if (calcExpressionValue === null) { - onChangeByCalcExpressionNull(); + // calculatedExpression value is a number + onChangeByCalcExpressionDecimal(calcExpressionValue); return () => clearTimeout(timeoutId); } - - // calculatedExpression value is a number - onChangeByCalcExpressionDecimal(calcExpressionValue); - return () => clearTimeout(timeoutId); } } + + return () => {}; }, // Only trigger this effect if calculatedExpression of item changes // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/smart-forms-renderer/src/hooks/useIntegerCalculatedExpression.ts b/packages/smart-forms-renderer/src/hooks/useIntegerCalculatedExpression.ts index 2b88ddc1f..a4e22c799 100644 --- a/packages/smart-forms-renderer/src/hooks/useIntegerCalculatedExpression.ts +++ b/packages/smart-forms-renderer/src/hooks/useIntegerCalculatedExpression.ts @@ -46,35 +46,35 @@ function useIntegerCalculatedExpression( (exp) => exp.from === 'item' ); - if (!calcExpression) { - return; - } + if (calcExpression) { + // only update if calculated value is different from current value + if ( + calcExpression.value !== inputValue && + (typeof calcExpression.value === 'number' || calcExpression.value === null) + ) { + const calcExpressionValue = calcExpression.value; - // only update if calculated value is different from current value - if ( - calcExpression.value !== inputValue && - (typeof calcExpression.value === 'number' || calcExpression.value === null) - ) { - const calcExpressionValue = calcExpression.value; + if (calcExpressionValue !== parseInt(inputValue)) { + // update ui to show calculated value changes + setCalcExpUpdated(true); + const timeoutId = setTimeout(() => { + setCalcExpUpdated(false); + }, 500); - if (calcExpressionValue !== parseInt(inputValue)) { - // update ui to show calculated value changes - setCalcExpUpdated(true); - const timeoutId = setTimeout(() => { - setCalcExpUpdated(false); - }, 500); + // calculatedExpression value is null + if (calcExpressionValue === null) { + onChangeByCalcExpressionNull(); + return () => clearTimeout(timeoutId); + } - // calculatedExpression value is null - if (calcExpressionValue === null) { - onChangeByCalcExpressionNull(); + // calculatedExpression value is a number + onChangeByCalcExpressionInteger(calcExpressionValue); return () => clearTimeout(timeoutId); } - - // calculatedExpression value is a number - onChangeByCalcExpressionInteger(calcExpressionValue); - return () => clearTimeout(timeoutId); } } + + return () => {}; }, // Only trigger this effect if calculatedExpression of item changes // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/smart-forms-renderer/src/hooks/useQuantityCalculatedExpression.ts b/packages/smart-forms-renderer/src/hooks/useQuantityCalculatedExpression.ts index 84cb5fb3e..86ad0b57f 100644 --- a/packages/smart-forms-renderer/src/hooks/useQuantityCalculatedExpression.ts +++ b/packages/smart-forms-renderer/src/hooks/useQuantityCalculatedExpression.ts @@ -66,108 +66,111 @@ function useQuantityCalculatedExpression( (exp) => exp.from === 'item' ); - if (!calcExpression) { - return; - } + if (calcExpression) { + // only update if calculated value is different from current value + if ( + calcExpression.value !== inputValue && + (typeof calcExpression.value === 'number' || + typeof calcExpression.value === 'string' || + calcExpression.value === null) + ) { + // Null path + if (calcExpression.value === null) { + onChangeByCalcExpressionNull(); + return; + } - // only update if calculated value is different from current value - if ( - calcExpression.value !== inputValue && - (typeof calcExpression.value === 'number' || - typeof calcExpression.value === 'string' || - calcExpression.value === null) - ) { - // Null path - if (calcExpression.value === null) { - onChangeByCalcExpressionNull(); - return; - } + // Number path + if (typeof calcExpression.value === 'number') { + const calcExpressionValue = + typeof precision === 'number' + ? parseFloat(calcExpression.value.toFixed(precision)) + : calcExpression.value; + + // only update if calculated value is different from current value + if (calcExpressionValue !== parseFloat(inputValue)) { + // update ui to show calculated value changes + setCalcExpUpdated(true); + const timeoutId = setTimeout(() => { + setCalcExpUpdated(false); + }, 500); + + // calculatedExpression value is null + if (calcExpressionValue === null) { + onChangeByCalcExpressionNull(); + return () => clearTimeout(timeoutId); + } - // Number path - if (typeof calcExpression.value === 'number') { - const calcExpressionValue = - typeof precision === 'number' - ? parseFloat(calcExpression.value.toFixed(precision)) - : calcExpression.value; - - // only update if calculated value is different from current value - if (calcExpressionValue !== parseFloat(inputValue)) { - // update ui to show calculated value changes - setCalcExpUpdated(true); - const timeoutId = setTimeout(() => { - setCalcExpUpdated(false); - }, 500); - - // calculatedExpression value is null - if (calcExpressionValue === null) { - onChangeByCalcExpressionNull(); + // calculatedExpression value is a number + onChangeByCalcExpressionDecimal(calcExpressionValue); return () => clearTimeout(timeoutId); } - - // calculatedExpression value is a number - onChangeByCalcExpressionDecimal(calcExpressionValue); - return () => clearTimeout(timeoutId); } - } - // String path (quantity) - if (typeof calcExpression.value === 'string') { - try { - const [value, unitCode] = calcExpression.value.split(' '); - const unitCodeFormatted = unitCode.replace(/'/g, ''); - - const ucumValueSet = 'http://hl7.org/fhir/ValueSet/ucum-units'; - const ucumSystem = 'http://unitsofmeasure.org'; - - validateCodePromise( - ucumValueSet, - ucumSystem, - unitCodeFormatted, - TERMINOLOGY_SERVER_URL - ).then((validateCodeResponse) => { - // Return early if validate-code request fails - if (!validateCodeResponse) { + // String path (quantity) + if (typeof calcExpression.value === 'string') { + try { + const [value, unitCode] = calcExpression.value.split(' '); + const unitCodeFormatted = unitCode?.replace(/'/g, ''); + + const ucumValueSet = 'http://hl7.org/fhir/ValueSet/ucum-units'; + const ucumSystem = 'http://unitsofmeasure.org'; + + if (!value || !unitCodeFormatted) { onChangeByCalcExpressionNull(); return; } - if (validateCodeResponse.parameter) { - const systemParameter = validateCodeResponse.parameter.find( - (p) => p.name === 'system' - ) as SystemParameter; - const codeParameter = validateCodeResponse.parameter.find( - (p) => p.name === 'code' - ) as CodeParameter; - const displayParameter = validateCodeResponse.parameter.find( - (p) => p.name === 'display' - ) as DisplayParameter; - if ( - systemParameter.valueUri && - codeParameter.valueCode && - displayParameter.valueString - ) { - // update ui to show calculated value changes - setCalcExpUpdated(true); - const timeoutId = setTimeout(() => { - setCalcExpUpdated(false); - }, 500); - - onChangeByCalcExpressionQuantity( - parseFloat(value), - systemParameter.valueUri, - codeParameter.valueCode, + validateCodePromise( + ucumValueSet, + ucumSystem, + unitCodeFormatted, + TERMINOLOGY_SERVER_URL + ).then((validateCodeResponse) => { + // Return early if validate-code request fails + if (validateCodeResponse?.parameter) { + const systemParameter = validateCodeResponse.parameter.find( + (p) => p.name === 'system' + ) as SystemParameter; + const codeParameter = validateCodeResponse.parameter.find( + (p) => p.name === 'code' + ) as CodeParameter; + const displayParameter = validateCodeResponse.parameter.find( + (p) => p.name === 'display' + ) as DisplayParameter; + if ( + systemParameter.valueUri && + codeParameter.valueCode && displayParameter.valueString - ); - return () => clearTimeout(timeoutId); + ) { + // update ui to show calculated value changes + setCalcExpUpdated(true); + const timeoutId = setTimeout(() => { + setCalcExpUpdated(false); + }, 500); + + onChangeByCalcExpressionQuantity( + parseFloat(value), + systemParameter.valueUri, + codeParameter.valueCode, + displayParameter.valueString + ); + return () => clearTimeout(timeoutId); + } } - } - }); - } catch (e) { - console.error(e); - onChangeByCalcExpressionNull(); + + onChangeByCalcExpressionNull(); + return () => {}; + }); + } catch (e) { + console.error(e); + onChangeByCalcExpressionNull(); + } } } } + + return () => {}; }, // Only trigger this effect if calculatedExpression of item changes // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/smart-forms-renderer/src/hooks/useStringCalculatedExpression.ts b/packages/smart-forms-renderer/src/hooks/useStringCalculatedExpression.ts index 70b2b4a6a..71403b809 100644 --- a/packages/smart-forms-renderer/src/hooks/useStringCalculatedExpression.ts +++ b/packages/smart-forms-renderer/src/hooks/useStringCalculatedExpression.ts @@ -45,38 +45,38 @@ function useStringCalculatedExpression( (exp) => exp.from === 'item' ); - if (!calcExpression) { - return; - } + if (calcExpression) { + // only update if calculated value is different from current value + if ( + calcExpression.value !== inputValue && + (typeof calcExpression.value === 'string' || + typeof calcExpression.value === 'number' || + calcExpression.value === null) + ) { + // update ui to show calculated value changes + setCalcExpUpdated(true); + const timeoutId = setTimeout(() => { + setCalcExpUpdated(false); + }, 500); + + // calculatedExpression value is null + if (calcExpression.value === null) { + onChangeByCalcExpressionNull(); + return () => clearTimeout(timeoutId); + } - // only update if calculated value is different from current value - if ( - calcExpression.value !== inputValue && - (typeof calcExpression.value === 'string' || - typeof calcExpression.value === 'number' || - calcExpression.value === null) - ) { - // update ui to show calculated value changes - setCalcExpUpdated(true); - const timeoutId = setTimeout(() => { - setCalcExpUpdated(false); - }, 500); + // calculatedExpression value is a string or number + const newInputValue = + typeof calcExpression.value === 'string' + ? calcExpression.value + : calcExpression.value.toString(); - // calculatedExpression value is null - if (calcExpression.value === null) { - onChangeByCalcExpressionNull(); + onChangeByCalcExpressionString(newInputValue); return () => clearTimeout(timeoutId); } - - // calculatedExpression value is a string or number - const newInputValue = - typeof calcExpression.value === 'string' - ? calcExpression.value - : calcExpression.value.toString(); - - onChangeByCalcExpressionString(newInputValue); - return () => clearTimeout(timeoutId); } + + return () => {}; }, // Only trigger this effect if calculatedExpression of item changes // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/smart-forms-renderer/src/hooks/useTerminologyServerQuery.ts b/packages/smart-forms-renderer/src/hooks/useTerminologyServerQuery.ts index 721a1b9e7..e4e1a77a5 100644 --- a/packages/smart-forms-renderer/src/hooks/useTerminologyServerQuery.ts +++ b/packages/smart-forms-renderer/src/hooks/useTerminologyServerQuery.ts @@ -51,8 +51,9 @@ function useTerminologyServerQuery( } // attempt to get url from contained value sets when loading questionnaire - if (processedValueSetUrls[answerValueSetUrl]) { - answerValueSetUrl = processedValueSetUrls[answerValueSetUrl]; + const processedValueSetUrl = processedValueSetUrls[answerValueSetUrl]; + if (processedValueSetUrl) { + answerValueSetUrl = processedValueSetUrl; } const urlWithTrailingAmpersand = diff --git a/packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts b/packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts index 48384ba6b..c44ee5ee7 100644 --- a/packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts +++ b/packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts @@ -61,12 +61,12 @@ function useValueSetCodings(qItem: QuestionnaireItem): { // attempt to get codings from value sets preprocessed when loading questionnaire if (processedValueSetCodings[cleanValueSetUrl]) { - return processedValueSetCodings[cleanValueSetUrl]; + return processedValueSetCodings[cleanValueSetUrl] ?? []; } // attempt to get codings from cached queried value sets if (cachedValueSetCodings[cleanValueSetUrl]) { - return cachedValueSetCodings[cleanValueSetUrl]; + return cachedValueSetCodings[cleanValueSetUrl] ?? []; } } @@ -84,14 +84,17 @@ function useValueSetCodings(qItem: QuestionnaireItem): { const contextMap: Record = {}; // get answer expression resource from launch contexts - if (launchContexts[variable]) { - const resourceType = launchContexts[variable].extension[1].valueCode; + const launchContext = launchContexts[variable]; + const xFhirQueryVariable = xFhirQueryVariables[variable]; + + if (launchContext) { + const resourceType = launchContext.extension[1].valueCode; const resource = getResourceFromLaunchContext(resourceType, patient, user, encounter); if (resource) { contextMap[variable] = resource; } - } else if (xFhirQueryVariables[variable]) { - const resource = xFhirQueryVariables[variable].result; + } else if (xFhirQueryVariable) { + const resource = xFhirQueryVariable.result; if (resource) { contextMap[variable] = resource; } diff --git a/packages/smart-forms-renderer/src/stores/questionnaireStore.ts b/packages/smart-forms-renderer/src/stores/questionnaireStore.ts index b1dcb969a..f8a0ffdb2 100644 --- a/packages/smart-forms-renderer/src/stores/questionnaireStore.ts +++ b/packages/smart-forms-renderer/src/stores/questionnaireStore.ts @@ -27,8 +27,8 @@ import type { LaunchContext } from '../interfaces/populate.interface'; import type { CalculatedExpression } from '../interfaces/calculatedExpression.interface'; import type { EnableWhenExpressions, EnableWhenItems } from '../interfaces/enableWhen.interface'; import type { AnswerExpression } from '../interfaces/answerExpression.interface'; -import type { Tabs } from '../interfaces/tab.interface'; -import type { Pages } from '../interfaces/page.interface'; +import type { Tab, Tabs } from '../interfaces/tab.interface'; +import type { Page, Pages } from '../interfaces/page.interface'; import { mutateRepeatEnableWhenItemInstances, updateEnableWhenItemAnswer @@ -269,7 +269,7 @@ export const questionnaireStore = createStore()((set, ge set(() => ({ tabs: { ...tabs, - [tabLinkId]: { ...tabs[tabLinkId], isComplete: !tabs[tabLinkId].isComplete } + [tabLinkId]: { ...tabs[tabLinkId], isComplete: !tabs[tabLinkId]?.isComplete } as Tab } })); }, @@ -278,7 +278,7 @@ export const questionnaireStore = createStore()((set, ge set(() => ({ pages: { ...pages, - [pageLinkId]: { ...pages[pageLinkId], isComplete: !pages[pageLinkId].isComplete } + [pageLinkId]: { ...pages[pageLinkId], isComplete: !pages[pageLinkId]?.isComplete } as Page } })); }, @@ -296,7 +296,7 @@ export const questionnaireStore = createStore()((set, ge const itemLinkedQuestions = enableWhenLinkedQuestions[linkId]; const updatedEnableWhenItems = updateEnableWhenItemAnswer( { ...enableWhenItems }, - itemLinkedQuestions, + itemLinkedQuestions ?? [], linkId, newAnswer, parentRepeatGroupIndex @@ -365,7 +365,7 @@ export const questionnaireStore = createStore()((set, ge calculatedExpressions: updatedCalculatedExpressions, fhirPathContext: updatedFhirPathContext })); - return 0; + return; } set(() => ({ diff --git a/packages/smart-forms-renderer/src/utils/calculatedExpression.ts b/packages/smart-forms-renderer/src/utils/calculatedExpression.ts index 6c395f18e..085c0f611 100644 --- a/packages/smart-forms-renderer/src/utils/calculatedExpression.ts +++ b/packages/smart-forms-renderer/src/utils/calculatedExpression.ts @@ -82,6 +82,9 @@ export function evaluateInitialCalculatedExpressions( for (const linkId in initialCalculatedExpressions) { const itemCalcExpressions = calculatedExpressions[linkId]; + if (!itemCalcExpressions) { + continue; + } for (const calcExpression of itemCalcExpressions) { try { @@ -124,6 +127,9 @@ export function evaluateCalculatedExpressions( let isUpdated = false; for (const linkId in calculatedExpressions) { const itemCalcExpressions = calculatedExpressions[linkId]; + if (!itemCalcExpressions) { + continue; + } for (const calcExpression of itemCalcExpressions) { try { @@ -168,11 +174,11 @@ export function initialiseCalculatedExpressionValues( // Filter calculated expressions, only preserve key-value pairs with values const calculatedExpressionsWithValues: Record = {}; for (const linkId in calculatedExpressions) { - const itemCalcExpressionsWithValues = calculatedExpressions[linkId].filter( + const itemCalcExpressionsWithValues = calculatedExpressions[linkId]?.filter( (calcExpression) => calcExpression.value !== undefined ); - if (itemCalcExpressionsWithValues.length > 0) { + if (itemCalcExpressionsWithValues && itemCalcExpressionsWithValues.length > 0) { calculatedExpressionsWithValues[linkId] = itemCalcExpressionsWithValues; } } diff --git a/packages/smart-forms-renderer/src/utils/choice.ts b/packages/smart-forms-renderer/src/utils/choice.ts index bd9cb0a6f..1bebe72be 100644 --- a/packages/smart-forms-renderer/src/utils/choice.ts +++ b/packages/smart-forms-renderer/src/utils/choice.ts @@ -144,12 +144,14 @@ export function getQrChoiceValue( ): string | null { if (qrChoice.answer && qrChoice.answer.length > 0) { const answer = qrChoice['answer'][0]; - if (answer['valueCoding']) { - return answer.valueCoding.code ? answer.valueCoding.code : ''; - } else if (answer['valueString'] !== undefined) { - return answer.valueString; - } else if (answer['valueInteger']) { - return answer.valueInteger.toString(); + if (answer) { + if (answer['valueCoding']) { + return answer.valueCoding.code ? answer.valueCoding.code : ''; + } else if (answer['valueString'] !== undefined) { + return answer.valueString; + } else if (answer['valueInteger']) { + return answer.valueInteger.toString(); + } } } diff --git a/packages/smart-forms-renderer/src/utils/enableWhen.ts b/packages/smart-forms-renderer/src/utils/enableWhen.ts index 8cf13314f..2e253731f 100644 --- a/packages/smart-forms-renderer/src/utils/enableWhen.ts +++ b/packages/smart-forms-renderer/src/utils/enableWhen.ts @@ -43,14 +43,15 @@ export function createEnableWhenLinkedQuestions(enableWhenItems: EnableWhenItems for (const items of [singleItems, repeatItems]) { for (const linkId in items) { - items[linkId].linked.forEach((linkedItem) => { + items[linkId]?.linked.forEach((linkedItem) => { const linkQId = linkedItem.enableWhen.question; - if (!linkedQuestionsMap[linkQId]) { - linkedQuestionsMap[linkQId] = []; + let linkedQuestions = linkedQuestionsMap[linkQId]; + if (!linkedQuestions) { + linkedQuestions = []; } - if (!linkedQuestionsMap[linkQId].includes(linkId)) { - linkedQuestionsMap[linkQId].push(linkId); + if (!linkedQuestions.includes(linkId)) { + linkedQuestions.push(linkId); } }); } @@ -179,20 +180,25 @@ export function mutateRepeatEnableWhenItemInstances( const { repeatItems } = items; for (const linkId in repeatItems) { - for (const linkedItem of repeatItems[linkId].linked) { + const repeatItem = repeatItems[linkId]; + if (!repeatItem) { + continue; + } + + for (const linkedItem of repeatItem.linked) { if (linkedItem.parentLinkId !== parentRepeatGroupLinkId) { continue; } if (actionType === 'add') { linkedItem.answers.splice(parentRepeatGroupIndex, 0); - repeatItems[linkId].enabledIndexes[parentRepeatGroupIndex] = checkItemIsEnabledRepeat( - repeatItems[linkId], + repeatItem.enabledIndexes[parentRepeatGroupIndex] = checkItemIsEnabledRepeat( + repeatItem, parentRepeatGroupIndex ); } else if (actionType === 'remove') { linkedItem.answers.splice(parentRepeatGroupIndex, 1); - repeatItems[linkId].enabledIndexes.splice(parentRepeatGroupIndex, 1); + repeatItem.enabledIndexes.splice(parentRepeatGroupIndex, 1); } } } @@ -262,6 +268,10 @@ export function setInitialAnswers( if (initialAnswers) { for (const linkId in initialAnswers) { const linkedQuestions = linkedQuestionsMap[linkId]; + if (!linkedQuestions) { + continue; + } + const newAnswer = initialAnswers[linkId]; updatedItems = updateEnableWhenItemAnswer( @@ -293,26 +303,29 @@ export function updateEnableWhenItemAnswer( for (const linkedQuestion of linkedQuestions) { // Linked question is in single items - if (singleItems[linkedQuestion]) { + const singleItem = singleItems[linkedQuestion]; + if (singleItem) { // Update modified linked answer - singleItems[linkedQuestion].linked.forEach((linkedItem) => { + singleItem.linked.forEach((linkedItem) => { if (linkedItem.enableWhen.question === linkId) { linkedItem.answer = newAnswer ?? undefined; } }); // Update enabled status of modified enableWhenItem - singleItems[linkedQuestion].isEnabled = checkItemIsEnabledSingle(singleItems[linkedQuestion]); + singleItem.isEnabled = checkItemIsEnabledSingle(singleItem); continue; } // Linked question is in repeat items - if (repeatItems[linkedQuestion] && parentRepeatGroupIndex !== null) { + const repeatItem = repeatItems[linkedQuestion]; + if (repeatItem && parentRepeatGroupIndex !== null) { // Update modified linked answer - repeatItems[linkedQuestion].linked.forEach((linkedItem) => { + repeatItem.linked.forEach((linkedItem) => { if (linkedItem.enableWhen.question === linkId) { if (newAnswer) { - linkedItem.answers[parentRepeatGroupIndex] = newAnswer[0] ?? undefined; + linkedItem.answers[parentRepeatGroupIndex] = + newAnswer[0] as QuestionnaireResponseItemAnswer; } else { delete linkedItem.answers[parentRepeatGroupIndex]; } @@ -320,8 +333,8 @@ export function updateEnableWhenItemAnswer( }); // Update enabled status of modified enableWhenItem - repeatItems[linkedQuestion].enabledIndexes[parentRepeatGroupIndex] = checkItemIsEnabledRepeat( - repeatItems[linkedQuestion], + repeatItem.enabledIndexes[parentRepeatGroupIndex] = checkItemIsEnabledRepeat( + repeatItem, parentRepeatGroupIndex ); } @@ -429,27 +442,33 @@ function initialiseUnansweredBooleans(items: EnableWhenItems): EnableWhenItems { // Initialise unanswered booleans for enableWhen single items for (const linkId in singleItems) { - const checkedIsEnabledItems = singleItems[linkId].linked.map((linkedItem) => + const singleItem = singleItems[linkId]; + if (!singleItem) { + continue; + } + + const checkedIsEnabledItems = singleItem.linked.map((linkedItem) => evaluateNonExistentAnswers(linkedItem.enableWhen) ); - singleItems[linkId].isEnabled = evaluateEnableBehaviour( + singleItem.isEnabled = evaluateEnableBehaviour( checkedIsEnabledItems, - singleItems[linkId].enableBehavior + singleItem.enableBehavior ); } // Initialise unanswered booleans for enableWhen repeat items for (const linkId in repeatItems) { - const checkedIsEnabledItems = repeatItems[linkId].linked.map((linkedItem) => + const repeatItem = repeatItems[linkId]; + if (!repeatItem) { + continue; + } + const checkedIsEnabledItems = repeatItem.linked.map((linkedItem) => evaluateNonExistentAnswers(linkedItem.enableWhen) ); - const isEnabled = evaluateEnableBehaviour( - checkedIsEnabledItems, - repeatItems[linkId].enableBehavior - ); - repeatItems[linkId].enabledIndexes = repeatItems[linkId].enabledIndexes.map(() => isEnabled); + const isEnabled = evaluateEnableBehaviour(checkedIsEnabledItems, repeatItem.enableBehavior); + repeatItem.enabledIndexes = repeatItem.enabledIndexes.map(() => isEnabled); } return items; diff --git a/packages/smart-forms-renderer/src/utils/enableWhenExpression.ts b/packages/smart-forms-renderer/src/utils/enableWhenExpression.ts index 73f6d731c..2115787ef 100644 --- a/packages/smart-forms-renderer/src/utils/enableWhenExpression.ts +++ b/packages/smart-forms-renderer/src/utils/enableWhenExpression.ts @@ -85,8 +85,12 @@ function evaluateEnableWhenSingleExpressions( } { let isUpdated = false; for (const linkId in enableWhenSingleExpressions) { - const initialValue = enableWhenSingleExpressions[linkId].isEnabled; - const expression = enableWhenSingleExpressions[linkId].expression; + const enableWhenSingleExpression = enableWhenSingleExpressions[linkId]; + const initialValue = enableWhenSingleExpression?.isEnabled; + const expression = enableWhenSingleExpression?.expression; + if (!enableWhenSingleExpression || !expression) { + continue; + } try { const result = fhirpath.evaluate('', expression, updatedFhirPathContext, fhirpath_r4_model); @@ -94,29 +98,29 @@ function evaluateEnableWhenSingleExpressions( // Update enableWhenExpressions if length of result array > 0 // Only update when current isEnabled value is different from the result, otherwise it will result in an infinite loop as per issue #733 if (result.length > 0 && initialValue !== result[0] && typeof result[0] === 'boolean') { - enableWhenSingleExpressions[linkId].isEnabled = result[0]; + enableWhenSingleExpression.isEnabled = result[0]; isUpdated = true; } // Update isEnabled value to false if no result is returned if (result.length === 0 && initialValue !== false) { - enableWhenSingleExpressions[linkId].isEnabled = false; + enableWhenSingleExpression.isEnabled = false; isUpdated = true; } // handle intersect edge case - evaluate() returns empty array if result is false if ( - enableWhenSingleExpressions[linkId].expression.includes('intersect') && + enableWhenSingleExpression.expression.includes('intersect') && result.length === 0 && initialValue !== false ) { - enableWhenSingleExpressions[linkId].isEnabled = false; + enableWhenSingleExpression.isEnabled = false; isUpdated = true; } } catch (e) { console.warn( e.message, - `LinkId: ${linkId}\nExpression: ${enableWhenSingleExpressions[linkId].expression}` + `LinkId: ${linkId}\nExpression: ${enableWhenSingleExpression.expression}` ); } } @@ -148,17 +152,20 @@ function evaluateEnableWhenRepeatExpressions( let aggregatedUpdated = false; for (const linkId in enableWhenRepeatExpressions) { // Get number of repeat group instances in the QR - const enableWhenExpression = enableWhenRepeatExpressions[linkId]; + const enableWhenRepeatExpression = enableWhenRepeatExpressions[linkId]; + if (!enableWhenRepeatExpression) { + continue; + } const numOfInstances = getNumOfEnableWhenExpressionItemInstances( - enableWhenExpression, + enableWhenRepeatExpression, fhirPathContext ); if (!numOfInstances) { continue; } - const lastLinkIdIndex = enableWhenExpression.expression.lastIndexOf('.where(linkId'); + const lastLinkIdIndex = enableWhenRepeatExpression.expression.lastIndexOf('.where(linkId'); if (lastLinkIdIndex === -1) { continue; } @@ -167,13 +174,13 @@ function evaluateEnableWhenRepeatExpressions( const { isEnabled, isUpdated } = evaluateEnableWhenRepeatExpressionInstance( linkId, fhirPathContext, - enableWhenExpression, + enableWhenRepeatExpression, lastLinkIdIndex, i ); if (typeof isEnabled === 'boolean') { - enableWhenRepeatExpressions[linkId].enabledIndexes[i] = isEnabled; + enableWhenRepeatExpression.enabledIndexes[i] = isEnabled; } aggregatedUpdated = aggregatedUpdated || isUpdated; @@ -298,7 +305,12 @@ export function mutateRepeatEnableWhenExpressionInstances( let isUpdated = false; for (const linkId in repeatExpressions) { - if (repeatExpressions[linkId].parentLinkId !== parentRepeatGroupLinkId) { + const repeatExpression = repeatExpressions[linkId]; + if (!repeatExpression) { + continue; + } + + if (repeatExpression.parentLinkId !== parentRepeatGroupLinkId) { continue; } @@ -306,16 +318,16 @@ export function mutateRepeatEnableWhenExpressionInstances( const { isEnabled } = evaluateEnableWhenRepeatExpressionInstance( linkId, updatedFhirPathContext, - repeatExpressions[linkId], - repeatExpressions[linkId].expression.lastIndexOf('.where(linkId'), + repeatExpression, + repeatExpression.expression.lastIndexOf('.where(linkId'), parentRepeatGroupIndex ); if (typeof isEnabled === 'boolean') { - repeatExpressions[linkId].enabledIndexes[parentRepeatGroupIndex] = isEnabled; + repeatExpression.enabledIndexes[parentRepeatGroupIndex] = isEnabled; } } else if (actionType === 'remove') { - repeatExpressions[linkId].enabledIndexes.splice(parentRepeatGroupIndex, 1); + repeatExpression.enabledIndexes.splice(parentRepeatGroupIndex, 1); } isUpdated = true; diff --git a/packages/smart-forms-renderer/src/utils/extractObservation.ts b/packages/smart-forms-renderer/src/utils/extractObservation.ts index 2f149fb49..19ae8907d 100644 --- a/packages/smart-forms-renderer/src/utils/extractObservation.ts +++ b/packages/smart-forms-renderer/src/utils/extractObservation.ts @@ -79,7 +79,7 @@ function extractObservationBasedRecursive( for (const answer of responseItem.answer) { const currentQItemExtractable = extraData.qItemMap[qItem.linkId]; - if (currentQItemExtractable.extractable) { + if (currentQItemExtractable?.extractable) { const observation = createObservation( qItem, extraData.qr, @@ -160,29 +160,31 @@ function mapQItemsExtractableRecursive( qItem: QuestionnaireItem, root?: Questionnaire, parent?: QuestionnaireItem, - qItemExtrableMap?: Record + qItemExtractableMap?: Record ): void { - if (!qItemExtrableMap) return; + if (!qItemExtractableMap) return; - if (!qItemExtrableMap[qItem.linkId]) { - qItemExtrableMap[qItem.linkId] = { extractable: false, extractCategories: [] }; + let qItemExtractable = qItemExtractableMap[qItem.linkId]; + if (!qItemExtractable) { + qItemExtractable = { extractable: false, extractCategories: [] }; } // Check if questionnaire extractable const extension = qItem.extension?.find((e) => e.url === FHIR_OBSERVATION_EXTRACT_EXTENSION); if (extension?.valueBoolean || extension?.valueBoolean === false) { - qItemExtrableMap[qItem.linkId].extractable = extension?.valueBoolean ?? false; - } else if (parent && qItemExtrableMap[parent.linkId]) { - qItemExtrableMap[qItem.linkId].extractable = qItemExtrableMap[parent.linkId].extractable; - } else if (root && qItemExtrableMap[root.id ?? 'root']) { - qItemExtrableMap[qItem.linkId].extractable = qItemExtrableMap[root?.id ?? 'root'].extractable; + qItemExtractable.extractable = extension?.valueBoolean ?? false; + } else if (parent && qItemExtractableMap[parent.linkId]) { + qItemExtractable.extractable = qItemExtractableMap[parent.linkId]?.extractable ?? false; + } else if (root && qItemExtractableMap[root.id ?? 'root']) { + const rootExtractable = qItemExtractableMap[root?.id ?? 'root']; + qItemExtractable.extractable = rootExtractable?.extractable ?? false; } else { - qItemExtrableMap[qItem.linkId].extractable = false; + qItemExtractable.extractable = false; } // if questionnaire extractable, check for extract category - if (qItemExtrableMap[qItem.linkId].extractable) { + if (qItemExtractable.extractable) { const extractCategoryExts = qItem.extension ?.filter( (e) => e.url === FHIR_OBSERVATION_EXTRACT_CATEGORY_EXTENSION && e.valueCodeableConcept @@ -190,21 +192,21 @@ function mapQItemsExtractableRecursive( ?.map((e) => e.valueCodeableConcept) as CodeableConcept[] | undefined; if (extractCategoryExts) { - qItemExtrableMap[qItem.linkId].extractCategories = extractCategoryExts; - } else if (parent && qItemExtrableMap[parent.linkId].extractCategories.length) { - qItemExtrableMap[qItem.linkId].extractCategories = - qItemExtrableMap[parent.linkId].extractCategories; - } else if (root && qItemExtrableMap[root.id ?? 'root'].extractCategories.length) { - qItemExtrableMap[qItem.linkId].extractCategories = - qItemExtrableMap[root?.id ?? 'root'].extractCategories; + qItemExtractable.extractCategories = extractCategoryExts; + } else if (parent && qItemExtractableMap[parent.linkId]?.extractCategories.length) { + qItemExtractable.extractCategories = + qItemExtractableMap[parent.linkId]?.extractCategories ?? []; + } else if (root && qItemExtractableMap[root.id ?? 'root']?.extractCategories.length) { + const rootExtractable = qItemExtractableMap[root?.id ?? 'root']; + qItemExtractable.extractCategories = rootExtractable?.extractCategories ?? []; } else { - qItemExtrableMap[qItem.linkId].extractCategories = []; + qItemExtractable.extractCategories = []; } } if (qItem.item && qItem.item.length !== 0) { for (const qChildItem of qItem.item) { - mapQItemsExtractableRecursive(qChildItem, root, qItem, qItemExtrableMap); + mapQItemsExtractableRecursive(qChildItem, root, qItem, qItemExtractableMap); } } } diff --git a/packages/smart-forms-renderer/src/utils/fhirpath.ts b/packages/smart-forms-renderer/src/utils/fhirpath.ts index 18454fcc5..bfd4fe6f5 100644 --- a/packages/smart-forms-renderer/src/utils/fhirpath.ts +++ b/packages/smart-forms-renderer/src/utils/fhirpath.ts @@ -112,8 +112,11 @@ export function createFhirPathContext( for (const linkId in questionnaireResponseItemMap) { // For non-repeat groups, the same linkId will have only one item // For repeat groups, the same linkId will have multiple items - for (const qrItem of questionnaireResponseItemMap[linkId]) { - fhirPathContext = evaluateLinkIdVariables(qrItem, variablesFhirPath, fhirPathContext); + const qrItems = questionnaireResponseItemMap[linkId]; + if (qrItems) { + for (const qrItem of qrItems) { + fhirPathContext = evaluateLinkIdVariables(qrItem, variablesFhirPath, fhirPathContext); + } } } diff --git a/packages/smart-forms-renderer/src/utils/formChanges.ts b/packages/smart-forms-renderer/src/utils/formChanges.ts index 93643325a..388087151 100644 --- a/packages/smart-forms-renderer/src/utils/formChanges.ts +++ b/packages/smart-forms-renderer/src/utils/formChanges.ts @@ -107,7 +107,7 @@ function getItemChange( const itemType = itemTypes[diffItem.linkId]; const operation = answerDiffOperationSwitcher(operator); - if (operation) { + if (operation && itemType) { changedItems[diffItem.linkId] = { linkId: diffItem.linkId, itemType: itemType, diff --git a/packages/smart-forms-renderer/src/utils/groupTable.ts b/packages/smart-forms-renderer/src/utils/groupTable.ts index 0f2111322..8be10e9d7 100644 --- a/packages/smart-forms-renderer/src/utils/groupTable.ts +++ b/packages/smart-forms-renderer/src/utils/groupTable.ts @@ -25,6 +25,11 @@ export function reorderRows( ) { const result = Array.from(rows); const [removed] = result.splice(sourceIndex, 1); + + if (removed === undefined) { + return result; + } + result.splice(destinationIndex, 0, removed); return result; diff --git a/packages/smart-forms-renderer/src/utils/initialise.ts b/packages/smart-forms-renderer/src/utils/initialise.ts index f9917d927..426dff046 100644 --- a/packages/smart-forms-renderer/src/utils/initialise.ts +++ b/packages/smart-forms-renderer/src/utils/initialise.ts @@ -278,7 +278,7 @@ function createNewQuestionnaireResponseItem( qItem: QuestionnaireItem, initialValueAnswers: QuestionnaireResponseItemAnswer[] ): QuestionnaireResponseItem | null { - if (initialValueAnswers.length === 0) { + if (!initialValueAnswers[0]) { return null; } diff --git a/packages/smart-forms-renderer/src/utils/itemControl.ts b/packages/smart-forms-renderer/src/utils/itemControl.ts index dc1c66169..ebba820b7 100644 --- a/packages/smart-forms-renderer/src/utils/itemControl.ts +++ b/packages/smart-forms-renderer/src/utils/itemControl.ts @@ -227,7 +227,7 @@ export function getTextDisplayPrompt(qItem: QuestionnaireItem): string { for (const childItem of qItem.item) { if (childItem.type === 'display' && isSpecificItemControl(childItem, 'prompt')) { const promptText = `${childItem.text}`; - return promptText[0].toUpperCase() + promptText.substring(1); + return promptText[0]?.toUpperCase() + promptText.substring(1); } } } diff --git a/packages/smart-forms-renderer/src/utils/mapItem.ts b/packages/smart-forms-renderer/src/utils/mapItem.ts index a1a36266f..12f8152fe 100644 --- a/packages/smart-forms-renderer/src/utils/mapItem.ts +++ b/packages/smart-forms-renderer/src/utils/mapItem.ts @@ -45,7 +45,7 @@ export function getQrItemsIndex( // Create an array out of initial stored value if it is not an array initially if (!Array.isArray(storedValue)) { - storedValue = [storedValue]; + storedValue = [storedValue] as QuestionnaireResponseItem[]; } // Push new qrItem into array @@ -55,8 +55,13 @@ export function getQrItemsIndex( const qItemIndex = qItemsIndexMap[linkId]; // Assign either a qrItem array or a single qrItem based on whether it is a repeatGroup or not - const isRepeatGroup = - isRepeatItemAndNotCheckbox(qItems[qItemIndex]) && qItems[qItemIndex].type === 'group'; + let isRepeatGroup = false; + if (qItemIndex) { + const qItemAtIndex = qItems[qItemIndex]; + if (qItemAtIndex) { + isRepeatGroup = isRepeatItemAndNotCheckbox(qItemAtIndex) && qItemAtIndex.type === 'group'; + } + } qrItemsCollected[linkId] = isRepeatGroup ? [qrItem] : qrItem; } @@ -69,9 +74,11 @@ export function getQrItemsIndex( const qrItemOrItems = qrItemsCollected[qItem.linkId]; // If qItem is a repeat group, default its value to an array instead of undefined if (isRepeatItemAndNotCheckbox(qItem) && qItem.type === 'group') { - mapping[i] = qrItemOrItems ? qrItemsCollected[qItem.linkId] : []; + mapping[i] = qrItemOrItems + ? (qrItemsCollected[qItem.linkId] as QuestionnaireResponseItem) + : []; } else { - mapping[i] = qrItemsCollected[qItem.linkId]; + mapping[i] = qrItemsCollected[qItem.linkId] as QuestionnaireResponseItem; } return mapping; }, diff --git a/packages/smart-forms-renderer/src/utils/openChoice.ts b/packages/smart-forms-renderer/src/utils/openChoice.ts index 30bd3532d..02c1456fd 100644 --- a/packages/smart-forms-renderer/src/utils/openChoice.ts +++ b/packages/smart-forms-renderer/src/utils/openChoice.ts @@ -58,7 +58,7 @@ export function updateOpenLabelAnswer( // Only remove the last one if there are multiple matches, in case open label value is same as an option if (matchedIndexes.length > 0) { const lastMatchedIndex = matchedIndexes[matchedIndexes.length - 1]; - const newAnswers = answers.filter((answer, index) => index !== lastMatchedIndex); + const newAnswers = answers.filter((_, index) => index !== lastMatchedIndex); const newAnswersWithKey = newAnswers.map((answer) => ({ ...answer, id: answerKey })); return { diff --git a/packages/smart-forms-renderer/src/utils/qItem.ts b/packages/smart-forms-renderer/src/utils/qItem.ts index 6ce13b35e..90c247b1b 100644 --- a/packages/smart-forms-renderer/src/utils/qItem.ts +++ b/packages/smart-forms-renderer/src/utils/qItem.ts @@ -46,22 +46,22 @@ export function isHiddenByEnableWhen(params: isHiddenByEnableWhensParams): boole // Check enableWhen items const { singleItems, repeatItems } = enableWhenItems; if (singleItems[linkId]) { - return !singleItems[linkId].isEnabled; + return !singleItems[linkId]?.isEnabled; } if (repeatItems[linkId] && parentRepeatGroupIndex !== undefined) { - return !repeatItems[linkId].enabledIndexes[parentRepeatGroupIndex]; + return !repeatItems[linkId]?.enabledIndexes[parentRepeatGroupIndex]; } // Check enableWhenExpressions const { singleExpressions, repeatExpressions } = enableWhenExpressions; if (repeatExpressions[linkId] && parentRepeatGroupIndex !== undefined) { - return !repeatExpressions[linkId].enabledIndexes[parentRepeatGroupIndex]; + return !repeatExpressions[linkId]?.enabledIndexes[parentRepeatGroupIndex]; } if (singleExpressions[linkId]) { - return !enableWhenExpressions.singleExpressions[linkId].isEnabled; + return !enableWhenExpressions.singleExpressions[linkId]?.isEnabled; } return false; diff --git a/packages/smart-forms-renderer/src/utils/qrItem.ts b/packages/smart-forms-renderer/src/utils/qrItem.ts index 9d632d50c..c1f16df2d 100644 --- a/packages/smart-forms-renderer/src/utils/qrItem.ts +++ b/packages/smart-forms-renderer/src/utils/qrItem.ts @@ -45,7 +45,7 @@ export function removeNoAnswerQrItem( } // remove starting or trailing whitespace - if (qrItem['answer'] && qrItem['answer'][0]['valueString']) { + if (qrItem['answer'] && qrItem['answer'][0]?.['valueString']) { qrItem['answer'][0]['valueString'] = qrItem['answer'][0]['valueString'].trim(); } @@ -114,11 +114,20 @@ export function updateQrItemsInGroup( return; } - // Get actual sequence index of qrItem within qrGroup const newQrItemIndex = qItemsIndexMap[newQrItem.linkId]; + if (!newQrItemIndex) { + return; + } + + // Get actual sequence index of qrItem within qrGroup for (let i = 0; i < qrItemsRealIndexArr.length; i++) { + const currentRealIndex = qrItemsRealIndexArr[i]; + if (!currentRealIndex) { + continue; + } + // Add qrItem at the end of qrGroup if it is larger than the other indexes - if (newQrItemIndex > qrItemsRealIndexArr[i]) { + if (newQrItemIndex > currentRealIndex) { if (i === qrItemsRealIndexArr.length - 1) { qrItems.push(newQrItem); } @@ -126,7 +135,7 @@ export function updateQrItemsInGroup( } // Replace or delete qrItem at its supposed position if its index is already present within qrGroup - if (newQrItemIndex === qrItemsRealIndexArr[i]) { + if (newQrItemIndex === currentRealIndex) { // newQrItem has answer value if (newQrItem.item?.length || newQrItem.answer?.length) { qrItems[i] = newQrItem; @@ -139,7 +148,7 @@ export function updateQrItemsInGroup( } // Add qrItem at its supposed position if its index is not present within qrGroup - if (newQrItemIndex < qrItemsRealIndexArr[i]) { + if (newQrItemIndex < currentRealIndex) { qrItems.splice(i, 0, newQrItem); break; } @@ -155,9 +164,18 @@ export function updateQrItemsInGroup( // Get actual sequence index of qrItems within qrGroup const newQrItemIndex = qItemsIndexMap[newQrRepeatGroup.linkId]; + if (!newQrItemIndex) { + return; + } + for (let i = 0; i < qrItemsRealIndexArr.length; i++) { + const currentRealIndex = qrItemsRealIndexArr[i]; + if (!currentRealIndex) { + continue; + } + // Add qrItem at the end of qrGroup if it is larger than the other indexes - if (newQrItemIndex > qrItemsRealIndexArr[i]) { + if (newQrItemIndex > currentRealIndex) { if (i === qrItemsRealIndexArr.length - 1) { qrItems.push(...newQrItems); } @@ -165,7 +183,7 @@ export function updateQrItemsInGroup( } // Replace or delete qrItem at its supposed position if its index is already present within qrGroup - if (newQrItemIndex === qrItemsRealIndexArr[i]) { + if (newQrItemIndex === currentRealIndex) { // Get number of repeatGroupItems that has the same linkId present in qrGroup let repeatGroupItemCount = 0; while (newQrItemIndex === qrItemsRealIndexArr[i + repeatGroupItemCount]) { @@ -175,7 +193,7 @@ export function updateQrItemsInGroup( // Replace each repeat group qrItem with their new counterparts if (newQrItems.length === repeatGroupItemCount) { for (let j = 0; j < newQrItems.length; j++) { - qrItems[i + j] = newQrItems[j]; + qrItems[i + j] = newQrItems[j] as QuestionnaireResponseItem; } break; } @@ -185,9 +203,9 @@ export function updateQrItemsInGroup( // followed by adding an extra newQrItem behind the newly replaced qrItems for (let j = 0, k = repeatGroupItemCount; j < newQrItems.length; j++, k--) { if (k > 0) { - qrItems[i + j] = newQrItems[j]; + qrItems[i + j] = newQrItems[j] as QuestionnaireResponseItem; } else { - qrItems.splice(i + j, 0, newQrItems[j]); + qrItems.splice(i + j, 0, newQrItems[j] as QuestionnaireResponseItem); } } break; @@ -198,7 +216,7 @@ export function updateQrItemsInGroup( // followed by deleting the last newQrItem which wasn't replaced for (let j = 0; j < repeatGroupItemCount; j++) { if (j <= newQrItems.length - 1) { - qrItems[i + j] = newQrItems[j]; + qrItems[i + j] = newQrItems[j] as QuestionnaireResponseItem; } else { qrItems.splice(i + j, 1); } @@ -208,9 +226,9 @@ export function updateQrItemsInGroup( } // Add qrItem at its supposed position if its index is not present within qrGroup - if (newQrItemIndex < qrItemsRealIndexArr[i]) { + if (newQrItemIndex < currentRealIndex) { for (let j = 0; j < newQrItems.length; j++) { - qrItems.splice(i + j, 0, newQrItems[j]); + qrItems.splice(i + j, 0, newQrItems[j] as QuestionnaireResponseItem); } break; } diff --git a/packages/smart-forms-renderer/src/utils/questionnaireResponseStoreUtils/updatableResponseItems.ts b/packages/smart-forms-renderer/src/utils/questionnaireResponseStoreUtils/updatableResponseItems.ts index 9548419d1..a24403e72 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireResponseStoreUtils/updatableResponseItems.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireResponseStoreUtils/updatableResponseItems.ts @@ -53,7 +53,7 @@ function fillQuestionnaireResponseItemMap( ) { // linkId already exists in questionnaireResponseItemMap, it would be a repeat group if (qrItem.linkId in questionnaireResponseItemMap) { - questionnaireResponseItemMap[qrItem.linkId].push(qrItem); + questionnaireResponseItemMap[qrItem.linkId]?.push(qrItem); } // Add item to questionnaireResponseItemMap else { diff --git a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/addAdditionalVariables.ts b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/addAdditionalVariables.ts index 41feabc23..ad4110d75 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/addAdditionalVariables.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/addAdditionalVariables.ts @@ -29,7 +29,7 @@ export function addAdditionalVariables( const expression = variable.valueExpression; if (expression && expression.language && expression.name) { if (expression.language === 'text/fhirpath') { - existingVariables.fhirPathVariables['QuestionnaireLevel'].push(expression); + existingVariables.fhirPathVariables['QuestionnaireLevel']?.push(expression); } else if (expression.language === 'application/x-fhir-query') { existingVariables.xFhirQueryVariables[expression.name] = { valueExpression: expression diff --git a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/addDisplayToCodings.ts b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/addDisplayToCodings.ts index 30aa27229..d7f7eadcc 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/addDisplayToCodings.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/addDisplayToCodings.ts @@ -28,13 +28,16 @@ export async function addDisplayToProcessedCodings( const codeSystemLookupPromises: Record = {}; for (const key in processedCodings) { const codings = processedCodings[key]; - for (const coding of codings) { - if (!coding.display) { - const query = `system=${coding.system}&code=${coding.code}`; - codeSystemLookupPromises[query] = { - promise: getCodeSystemLookupPromise(query, terminologyServerUrl), - oldCoding: coding - }; + + if (codings) { + for (const coding of codings) { + if (!coding.display) { + const query = `system=${coding.system}&code=${coding.code}`; + codeSystemLookupPromises[query] = { + promise: getCodeSystemLookupPromise(query, terminologyServerUrl), + oldCoding: coding + }; + } } } } @@ -44,11 +47,13 @@ export async function addDisplayToProcessedCodings( for (const key in processedCodings) { const codings = processedCodings[key]; - for (const coding of codings) { - const lookUpKey = `system=${coding.system}&code=${coding.code}`; - const resolvedLookup = resolvedCodeSystemLookupPromises[lookUpKey]; - if (resolvedLookup?.newCoding?.display) { - coding.display = resolvedLookup.newCoding.display; + if (codings) { + for (const coding of codings) { + const lookUpKey = `system=${coding.system}&code=${coding.code}`; + const resolvedLookup = resolvedCodeSystemLookupPromises[lookUpKey]; + if (resolvedLookup?.newCoding?.display) { + coding.display = resolvedLookup.newCoding.display; + } } } } @@ -65,13 +70,16 @@ export async function addDisplayToAnswerOptions( const codeSystemLookupPromises: Record = {}; for (const key in answerOptions) { const options = answerOptions[key]; - for (const option of options) { - if (option.valueCoding && !option.valueCoding.display) { - const query = `system=${option.valueCoding.system}&code=${option.valueCoding.code}`; - codeSystemLookupPromises[query] = { - promise: getCodeSystemLookupPromise(query, terminologyServerUrl), - oldCoding: option.valueCoding - }; + + if (options) { + for (const option of options) { + if (option.valueCoding && !option.valueCoding.display) { + const query = `system=${option.valueCoding.system}&code=${option.valueCoding.code}`; + codeSystemLookupPromises[query] = { + promise: getCodeSystemLookupPromise(query, terminologyServerUrl), + oldCoding: option.valueCoding + }; + } } } } @@ -81,12 +89,14 @@ export async function addDisplayToAnswerOptions( for (const key in answerOptions) { const options = answerOptions[key]; - for (const option of options) { - if (option.valueCoding) { - const lookUpKey = `system=${option.valueCoding.system}&code=${option.valueCoding.code}`; - const resolvedLookup = resolvedCodeSystemLookupPromises[lookUpKey]; - if (resolvedLookup?.newCoding?.display) { - option.valueCoding.display = resolvedLookup.newCoding.display; + if (options) { + for (const option of options) { + if (option.valueCoding) { + const lookUpKey = `system=${option.valueCoding.system}&code=${option.valueCoding.code}`; + const resolvedLookup = resolvedCodeSystemLookupPromises[lookUpKey]; + if (resolvedLookup?.newCoding?.display) { + option.valueCoding.display = resolvedLookup.newCoding.display; + } } } } diff --git a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractContainedValueSets.ts b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractContainedValueSets.ts index 4545daa99..ddf5f16bc 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractContainedValueSets.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractContainedValueSets.ts @@ -68,10 +68,10 @@ export function extractContainedValueSets( * * @author Sean Fong */ -export function getValueSetUrlFromContained(valueSet: ValueSet): string { +export function getValueSetUrlFromContained(valueSet: ValueSet) { const urls = valueSet.compose?.include?.map((include) => include.valueSet?.[0] ? include.valueSet[0] : '' ); - return urls && urls.length > 0 ? urls[0] : ''; + return urls && urls.length > 0 ? urls[0] ?? '' : ''; } diff --git a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts index 960e05958..596c689c5 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts @@ -391,9 +391,11 @@ function initialiseEnableWhenExpressionRepeat( // Use the last linkId as the linkedItem, and get it's repeat group parent item's linkId // If both parent linkId matches, this enableWhenExpression is a repeat enableWhenExpression - const linkedParentItem = getRepeatGroupParentItem(questionnaire, lastLinkIdMatch); - if (parentLinkId === linkedParentItem?.linkId) { - return enableWhenExpression; + if (lastLinkIdMatch) { + const linkedParentItem = getRepeatGroupParentItem(questionnaire, lastLinkIdMatch); + if (parentLinkId === linkedParentItem?.linkId) { + return enableWhenExpression; + } } return null; diff --git a/packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts b/packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts index 1422f2125..9b79a072a 100644 --- a/packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts +++ b/packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts @@ -133,11 +133,18 @@ function removeEmptyAnswersFromItemRecursive( qItemIndex < qItems.length || qrItemIndex < qrItems.length; qItemIndex++ ) { + const qItemCurrent = qItems[qItemIndex]; + const qrItemCurrent = qrItems[qrItemIndex]; + + if (!qItemCurrent || !qrItemCurrent) { + continue; + } + // Save qrItem if linkIds of current qItem and qrItem are the same - if (qrItems[qrItemIndex] && qItems[qItemIndex].linkId === qrItems[qrItemIndex].linkId) { + if (qrItems[qrItemIndex] && qItemCurrent.linkId === qrItemCurrent.linkId) { const newQrItem = removeEmptyAnswersFromItemRecursive({ - qItem: qItems[qItemIndex], - qrItem: qrItems[qrItemIndex], + qItem: qItemCurrent, + qrItem: qrItemCurrent, enableWhenIsActivated, enableWhenItems, enableWhenExpressions @@ -149,9 +156,11 @@ function removeEmptyAnswersFromItemRecursive( // Decrement qItem index if the next qrItem is an answer from a repeatGroup // Essentially persisting the current qItem linked to be matched up with the next qrItem linkId + const qrItemNext = qrItems[qrItemIndex + 1]; if ( qrItems.length !== qrItemIndex + 1 && - qrItems[qrItemIndex].linkId === qrItems[qrItemIndex + 1].linkId + qrItemNext && + qrItemCurrent.linkId === qrItemNext.linkId ) { qItemIndex--; } diff --git a/packages/smart-forms-renderer/src/utils/repopulateItems.ts b/packages/smart-forms-renderer/src/utils/repopulateItems.ts index aad2c40a6..e38e282d5 100644 --- a/packages/smart-forms-renderer/src/utils/repopulateItems.ts +++ b/packages/smart-forms-renderer/src/utils/repopulateItems.ts @@ -112,7 +112,7 @@ export function generateItemsToRepopulate(populatedResponse: QuestionnaireRespon itemsToRepopulate[linkId] = { qItem: getQuestionnaireItem(sourceQuestionnaire, linkId), heading: getSectionHeading(sourceQuestionnaire, linkId, tabs), - oldQRItem: updatableResponseItems[linkId][0] + oldQRItem: updatableResponseItems[linkId]?.[0] }; } } @@ -237,7 +237,7 @@ function getItemsToRepopulateRecursive(params: getItemsToRepopulateRecursivePara enableWhenExpressions }) ) { - return null; + return; } // For repeat groups @@ -462,8 +462,12 @@ function retrieveSingleOldQRItem( oldQRItem: QuestionnaireResponseItem, itemsToRepopulate: Record ) { - const newQRItem = itemsToRepopulate[qItem.linkId]?.newQRItem; + const itemToRepopulate = itemsToRepopulate[qItem.linkId]; + if (!itemToRepopulate) { + return; + } + const newQRItem = itemToRepopulate.newQRItem; if (!newQRItem) { return; } @@ -473,7 +477,7 @@ function retrieveSingleOldQRItem( return; } - itemsToRepopulate[qItem.linkId].oldQRItem = oldQRItem; + itemToRepopulate.oldQRItem = oldQRItem; } function retrieveRepeatGroupOldQRItems( @@ -485,7 +489,12 @@ function retrieveRepeatGroupOldQRItems( return; } - const newQRItems = itemsToRepopulate[qItem.linkId]?.newQRItems; + const itemToRepopulate = itemsToRepopulate[qItem.linkId]; + if (!itemToRepopulate) { + return; + } + + const newQRItems = itemToRepopulate.newQRItems; if (!newQRItems) { return; } @@ -495,7 +504,7 @@ function retrieveRepeatGroupOldQRItems( return; } - itemsToRepopulate[qItem.linkId].oldQRItems = oldQRItems; + itemToRepopulate.oldQRItems = oldQRItems; } function retrieveGridGroupOldQRItems( @@ -508,7 +517,12 @@ function retrieveGridGroupOldQRItems( return; } - const newGridQRItem = itemsToRepopulate[qItem.linkId]?.newQRItem; + const itemToRepopulate = itemsToRepopulate[qItem.linkId]; + if (!itemToRepopulate) { + return; + } + + const newGridQRItem = itemToRepopulate.newQRItem; if (!newGridQRItem || !newGridQRItem.item) { return; } @@ -550,12 +564,12 @@ function retrieveGridGroupOldQRItems( } // Otherwise create both old and new grid qr item - itemsToRepopulate[qItem.linkId].newQRItem = { + itemToRepopulate.newQRItem = { linkId: qItem.linkId, text: qItem.text, item: newGridChildQRItemsToRepopulate }; - itemsToRepopulate[qItem.linkId].oldQRItem = { + itemToRepopulate.oldQRItem = { linkId: qItem.linkId, text: qItem.text, item: oldGridChildQRItems diff --git a/packages/smart-forms-renderer/tsconfig.json b/packages/smart-forms-renderer/tsconfig.json index 0cdf69ea0..4e659407c 100644 --- a/packages/smart-forms-renderer/tsconfig.json +++ b/packages/smart-forms-renderer/tsconfig.json @@ -6,16 +6,23 @@ "esModuleInterop": true, "moduleResolution": "node", "forceConsistentCasingInFileNames": true, - "useUnknownInCatchVariables": false, "strict": true, "skipLibCheck": true, "jsx": "react", "sourceMap": true, - "allowJs": true, "outDir": "lib", "declaration": true, - "checkJs": true, - "resolveJsonModule": true + "resolveJsonModule": true, + + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "noUnusedParameters": true, + + "useUnknownInCatchVariables": false }, "include": ["src"], "exclude": ["src/**/*.test.ts", "src/stories/**/*", "src/setup-jest.*", "lib"]