diff --git a/apps/smart-forms-app/package.json b/apps/smart-forms-app/package.json index 45759b2e2..c1a784f5d 100644 --- a/apps/smart-forms-app/package.json +++ b/apps/smart-forms-app/package.json @@ -60,7 +60,7 @@ "@jest/globals": "^29.5.0", "@sentry/cli": "^2.19.4", "@tanstack/react-query-devtools": "^4.29.6", - "@types/fhir": "^0.0.36", + "@types/fhir": "^0.0.37", "@types/jest": "^29.5.2", "@types/lodash.clonedeep": "^4.5.7", "@types/lodash.debounce": "^4.0.7", diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/QuestionnaireTableView.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/QuestionnaireTableView.tsx index 9d92ca720..fd26d7844 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/QuestionnaireTableView.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/QuestionnaireTableView.tsx @@ -16,7 +16,6 @@ */ import QuestionnaireListToolbar from './TableComponents/QuestionnaireListToolbar.tsx'; -import Scrollbar from '../../../../../components/Scrollbar/Scrollbar.tsx'; import { Fade, Table as MuiTable, TableBody, TableContainer, Typography } from '@mui/material'; import DashboardTableHead from '../DashboardTableHead.tsx'; import QuestionnaireTableRow from './TableComponents/QuestionnaireTableRow.tsx'; @@ -71,38 +70,36 @@ function QuestionnaireTableView(props: QuestionnaireTableViewProps) { onSearch={onSearch} /> - - - - - - {table.getRowModel().rows.map((row) => { - const rowData = row.original; - const isSelected = selectedQuestionnaire?.listItem.id === rowData.id; + + + + + {table.getRowModel().rows.map((row) => { + const rowData = row.original; + const isSelected = selectedQuestionnaire?.listItem.id === rowData.id; - return ( - onRowClick(rowData.id)} - /> - ); - })} - + return ( + onRowClick(rowData.id)} + /> + ); + })} + - {isEmpty || fetchStatus === 'error' || isInitialLoading ? ( - - ) : null} - - - + {isEmpty || fetchStatus === 'error' || isInitialLoading ? ( + + ) : null} + + diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/BooleanItem/BooleanItem.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/BooleanItem/BooleanItem.tsx index aad9542f2..c772a0bb6 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/BooleanItem/BooleanItem.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/BooleanItem/BooleanItem.tsx @@ -26,7 +26,6 @@ import { createEmptyQrItem } from '../../../../utils/qrItem.ts'; import { FullWidthFormComponentBox } from '../../../../../../components/Box/Box.styles.tsx'; import FieldGrid from '../FieldGrid.tsx'; import BooleanField from './BooleanField.tsx'; -import useInitialiseBooleanFalse from '../../../../hooks/useInitialiseBooleanFalse.ts'; import { Box } from '@mui/material'; interface BooleanItemProps @@ -49,8 +48,6 @@ function BooleanItem(props: BooleanItemProps) { checked = qrItem.answer[0].valueBoolean; } - useInitialiseBooleanFalse(qItem, qrItem, onQrItemChange); - // Event handlers function handleCheckedChange(newChecked: boolean) { onQrItemChange({ diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/DisplayItem/DisplayItem.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/DisplayItem/DisplayItem.tsx index 91fb7cb50..6e755b155 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/DisplayItem/DisplayItem.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/DisplayItem/DisplayItem.tsx @@ -19,6 +19,7 @@ import { memo } from 'react'; import type { QuestionnaireItem } from 'fhir/r4'; import { FullWidthFormComponentBox } from '../../../../../../components/Box/Box.styles.tsx'; import LabelText from '../QItemParts/LabelText.tsx'; +import { isSpecificItemControl } from '../../../../utils/itemControl.ts'; interface DisplayItemProps { qItem: QuestionnaireItem; @@ -27,6 +28,11 @@ interface DisplayItemProps { const DisplayItem = memo(function DisplayItem(props: DisplayItemProps) { const { qItem } = props; + const isContextDisplay = isSpecificItemControl(qItem, 'context-display'); + if (isContextDisplay) { + return null; + } + return ( diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/GroupItem/GroupHeading.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/GroupItem/GroupHeading.tsx index ec2734f20..566c5f9e8 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/GroupItem/GroupHeading.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/GroupItem/GroupHeading.tsx @@ -19,9 +19,10 @@ import { memo } from 'react'; import { Box, Divider } from '@mui/material'; import { QGroupHeadingTypography } from '../Typography.styles.ts'; import LabelText from '../QItemParts/LabelText.tsx'; -import CompleteTabButton from '../../Tabs/CompleteTabButton.tsx'; import type { PropsWithIsRepeatedAttribute } from '../../../../types/renderProps.interface.ts'; import type { QuestionnaireItem } from 'fhir/r4'; +import { getContextDisplays } from '../../../../utils/tabs.ts'; +import GroupHeadingIcon from './GroupHeadingIcon.tsx'; interface GroupHeadingProps extends PropsWithIsRepeatedAttribute { qItem: QuestionnaireItem; @@ -31,6 +32,8 @@ interface GroupHeadingProps extends PropsWithIsRepeatedAttribute { const GroupHeading = memo(function GroupHeading(props: GroupHeadingProps) { const { qItem, tabIsMarkedAsComplete, isRepeated } = props; + const contextDisplayItems = getContextDisplays(qItem); + if (isRepeated) { return null; } @@ -42,12 +45,11 @@ const GroupHeading = memo(function GroupHeading(props: GroupHeadingProps) { - {tabIsMarkedAsComplete !== undefined ? ( - - ) : null} + + {contextDisplayItems.map((item) => { + return ; + })} + {qItem.text ? : null} diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/GroupItem/GroupHeadingIcon.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/GroupItem/GroupHeadingIcon.tsx new file mode 100644 index 000000000..9923f1c30 --- /dev/null +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/GroupItem/GroupHeadingIcon.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2023 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 { memo } from 'react'; +import type { QuestionnaireItem } from 'fhir/r4'; +import useHidden from '../../../../hooks/useHidden.ts'; +import LabelText from '../QItemParts/LabelText.tsx'; + +interface GroupHeadingIconProps { + displayItem: QuestionnaireItem; +} + +const GroupHeadingIcon = memo(function GroupHeadingIcon(props: GroupHeadingIconProps) { + const { displayItem } = props; + + const itemIsHidden = useHidden(displayItem); + if (itemIsHidden) { + return null; + } + + return ; +}); + +export default GroupHeadingIcon; diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/QItemParts/LabelText.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/QItemParts/LabelText.tsx index 637466ea1..22e2bb63a 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/QItemParts/LabelText.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/QItemParts/LabelText.tsx @@ -32,15 +32,16 @@ const LabelText = memo(function LabelText(props: LabelTextProps) { // parse xHTML if found const xHtmlString = getXHtmlString(qItem); + if (xHtmlString) { - return {parse(xHtmlString)}; + return {parse(xHtmlString)}; } // parse xHTML if found const markdownString = getMarkdownString(qItem); if (markdownString) { return ( - + {markdownString} ); diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/Tables/QItemGroupTable.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/Tables/QItemGroupTable.tsx index 14c129cf3..0eeb13c94 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/Tables/QItemGroupTable.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/Tables/QItemGroupTable.tsx @@ -145,11 +145,6 @@ function QItemGroupTable(props: Props) { answeredQrItem.item = nullableQrItem.item; } - console.log('----'); - console.log(qItem); - console.log(answeredQrItem); - console.log('----'); - return ( state.switchTab); + const contextDisplayItems = getContextDisplays(qItem); + function handleTabClick() { switchTab(listIndex); window.scrollTo(0, 0); } return ( - - - {markedAsComplete ? ( - - - - ) : ( - - - + <> + + + {tabLabel} + + {contextDisplayItems.map((item) => { + return ; + })} + - - )} - - {tabLabel}} /> - + } + /> + + ); }); diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/Tabs/FormBodyTabList.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/Tabs/FormBodyTabList.tsx index 4bed4f101..962062cf5 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPage/Tabs/FormBodyTabList.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/Tabs/FormBodyTabList.tsx @@ -61,15 +61,14 @@ const FormBodyTabList = memo(function FormBodyTabList(props: FormBodyTabListProp const tabIsSelected = currentTabIndex.toString() === i.toString(); const tabLabel = getShortText(qItem) ?? qItem.text ?? ''; - const tabIsMarkedAsComplete = tabs[qItem.linkId].isComplete ?? false; return ( ); diff --git a/apps/smart-forms-app/src/features/renderer/hooks/useInitialiseBooleanFalse.ts b/apps/smart-forms-app/src/features/renderer/hooks/useInitialiseBooleanFalse.ts deleted file mode 100644 index e5c58acef..000000000 --- a/apps/smart-forms-app/src/features/renderer/hooks/useInitialiseBooleanFalse.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 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 { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4'; -import useQuestionnaireStore from '../../../stores/useQuestionnaireStore.ts'; -import { useEffect } from 'react'; -import { createEmptyQrItem } from '../utils/qrItem.ts'; - -function useInitialiseBooleanFalse( - questionnaireItem: QuestionnaireItem, - questionnaireResponseItem: QuestionnaireResponseItem, - onQrItemChange: (qrItem: QuestionnaireResponseItem) => unknown -) { - const initialValueBoolean = questionnaireResponseItem?.answer?.[0].valueBoolean; - - // Trigger enableWhen on init - special case - const enableWhenLinkedQuestions = useQuestionnaireStore( - (state) => state.enableWhenLinkedQuestions - ); - useEffect( - () => { - // if boolean item is an enableWhen linked question and it does not have an answer yet - // set default answer to false - to trigger enableWhen == false - if ( - questionnaireItem.linkId in enableWhenLinkedQuestions && - typeof initialValueBoolean === 'undefined' - ) { - onQrItemChange({ - ...createEmptyQrItem(questionnaireItem), - answer: [{ valueBoolean: false }] - }); - } - }, - // Only run effect on init - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); -} - -export default useInitialiseBooleanFalse; diff --git a/apps/smart-forms-app/src/features/renderer/utils/itemControl.ts b/apps/smart-forms-app/src/features/renderer/utils/itemControl.ts index 0e193f6d2..9259a20da 100644 --- a/apps/smart-forms-app/src/features/renderer/utils/itemControl.ts +++ b/apps/smart-forms-app/src/features/renderer/utils/itemControl.ts @@ -168,7 +168,26 @@ export function getDecimalPrecision(qItem: QuestionnaireItem): number | null { * @author Sean Fong */ export function getXHtmlString(qItem: QuestionnaireItem): string | null { - const itemControl = qItem.extension?.find( + let xHtmlString = null; + if (qItem.extension) { + xHtmlString = getXHtmlStringFromExtension(qItem.extension); + if (xHtmlString) { + return xHtmlString; + } + } + + if (qItem._text?.extension) { + xHtmlString = getXHtmlStringFromExtension(qItem._text?.extension); + if (xHtmlString) { + return xHtmlString; + } + } + + return null; +} + +export function getXHtmlStringFromExtension(extensions: Extension[]): string | null { + const itemControl = extensions?.find( (extension: Extension) => extension.url === 'http://hl7.org/fhir/StructureDefinition/rendering-xhtml' ); @@ -178,6 +197,7 @@ export function getXHtmlString(qItem: QuestionnaireItem): string | null { return itemControl.valueString; } } + return null; } diff --git a/apps/smart-forms-app/src/features/renderer/utils/tabs.ts b/apps/smart-forms-app/src/features/renderer/utils/tabs.ts index 09673c7bb..5109b8bd7 100644 --- a/apps/smart-forms-app/src/features/renderer/utils/tabs.ts +++ b/apps/smart-forms-app/src/features/renderer/utils/tabs.ts @@ -202,3 +202,14 @@ export function findNumOfVisibleTabs( }); return tabsWithVisibility.filter((tab) => tab.isVisible).length; } + +export function getContextDisplays(item: QuestionnaireItem): QuestionnaireItem[] { + if (!item.item || item.item.length === 0) { + return []; + } + + return item.item.filter( + (childItem) => + isSpecificItemControl(childItem, 'context-display') && childItem.type === 'display' + ); +} diff --git a/apps/smart-forms-app/src/utils/enableWhen.ts b/apps/smart-forms-app/src/utils/enableWhen.ts index 60a809d88..4b9b21c08 100644 --- a/apps/smart-forms-app/src/utils/enableWhen.ts +++ b/apps/smart-forms-app/src/utils/enableWhen.ts @@ -153,7 +153,7 @@ export function readInitialAnswers( const initialValuesMap: Record = {}; questionnaireResponse.item.forEach((item) => { - readQuestionnaireResponseItem(item, initialValuesMap, linkedQuestionsMap); + readQuestionnaireResponseItemRecursive(item, initialValuesMap, linkedQuestionsMap); }); return initialValuesMap; } @@ -163,7 +163,7 @@ export function readInitialAnswers( * * @author Sean Fong */ -function readQuestionnaireResponseItem( +function readQuestionnaireResponseItemRecursive( item: QuestionnaireResponseItem, initialValues: Record, linkedQuestionsMap: Record @@ -172,7 +172,7 @@ function readQuestionnaireResponseItem( if (items && items.length > 0) { // iterate through items of item recursively items.forEach((item) => { - readQuestionnaireResponseItem(item, initialValues, linkedQuestionsMap); + readQuestionnaireResponseItemRecursive(item, initialValues, linkedQuestionsMap); }); return; } @@ -264,6 +264,7 @@ export function assignPopulatedAnswersToEnableWhen( ): { initialisedItems: EnableWhenItems; linkedQuestions: Record } { const linkedQuestions = createEnableWhenLinkedQuestions(items); const initialAnswers = readInitialAnswers(questionnaireResponse, linkedQuestions); + items = initialiseBooleanFalses(items); const initialisedItems = Object.keys(initialAnswers).length > 0 @@ -272,3 +273,31 @@ export function assignPopulatedAnswersToEnableWhen( return { initialisedItems, linkedQuestions }; } + +function initialiseBooleanFalses(items: EnableWhenItems): EnableWhenItems { + for (const linkId in items) { + const checkedIsEnabledItems: boolean[] = []; + const enableWhenItemProperties = items[linkId]; + + for (const linkedItem of enableWhenItemProperties.linked) { + if (linkedItem.enableWhen.answerBoolean === true && linkedItem.enableWhen.operator === '!=') { + checkedIsEnabledItems.push(true); + continue; + } + + if (linkedItem.enableWhen.answerBoolean === false && linkedItem.enableWhen.operator === '=') { + checkedIsEnabledItems.push(true); + continue; + } + + checkedIsEnabledItems.push(false); + } + + enableWhenItemProperties.isEnabled = + enableWhenItemProperties.enableBehavior === 'any' + ? checkedIsEnabledItems.some((isEnabled) => isEnabled) + : checkedIsEnabledItems.every((isEnabled) => isEnabled); + } + + return items; +} diff --git a/package-lock.json b/package-lock.json index 88831ce0e..52fc5af64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,7 @@ "@jest/globals": "^29.5.0", "@sentry/cli": "^2.19.4", "@tanstack/react-query-devtools": "^4.29.6", - "@types/fhir": "^0.0.36", + "@types/fhir": "^0.0.37", "@types/jest": "^29.5.2", "@types/lodash.clonedeep": "^4.5.7", "@types/lodash.debounce": "^4.0.7", @@ -157,6 +157,12 @@ } } }, + "apps/smart-forms-app/node_modules/@types/fhir": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/@types/fhir/-/fhir-0.0.37.tgz", + "integrity": "sha512-fR1y6tPfDmxYDWN4JkJhuI5F5QpbaFVSoNo3pu9A6nzuoojANqg0UBnNZTVegTz/MilV3PSjyvFe6/vO55geKA==", + "dev": true + }, "apps/smart-forms-app/node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",