Skip to content

Commit

Permalink
Merge pull request #802 from aehrc/issue/692
Browse files Browse the repository at this point in the history
Issue/692
  • Loading branch information
fongsean authored May 20, 2024
2 parents 2b243ee + 82fc29b commit 59ce39c
Show file tree
Hide file tree
Showing 48 changed files with 745 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
* limitations under the License.
*/

export * from './utils';
export { parseFhirDateToDisplayDate } from './utils';
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ export function getNumOfSeparators(valueDate: string, seperator: string) {
return [...valueDate.matchAll(regex)].length;
}

/**
* Parse a FHIR date string to a date to be consumed and displayed by the DateItem component.
*
* @author Sean Fong
*/
export function parseFhirDateToDisplayDate(fhirDate: string): {
displayDate: string;
dateParseFail?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ interface GridGroupProps
groupCardElevation: number;
}

/**
* Main component to render a Group Grid (grid) Questionnaire item.
* @see {@link https://hl7.org/fhir/extensions/CodeSystem-questionnaire-item-control.html}
*
* @author Sean Fong
*/
function GridGroup(props: GridGroupProps) {
const { qItem, qrItem, groupCardElevation, showMinimalView, parentIsReadOnly, onQrItemChange } =
props;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ interface RepeatGroupProps
groupCardElevation: number;
}

/**
* Main component to render a repeating, group Questionnaire item.
* Store and manages the state of multiple instances of GroupItem in a repeating group.
*
* @author Sean Fong
*/
function RepeatGroup(props: RepeatGroupProps) {
const {
qItem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ interface RepeatItemProps
groupCardElevation: number;
}

/**
* Main component to render a repeating, non-group Questionnaire item.
*
* @author Sean Fong
*/
function RepeatItem(props: RepeatItemProps) {
const { qItem, qrItem, groupCardElevation, showMinimalView, parentIsReadOnly, onQrItemChange } =
props;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ interface SingleItemProps
groupCardElevation: number;
}

/**
* Main component to render a repeating, non-group Questionnaire item.
* Store and manages the state of multiple instances of SingleItem in a repeating item.
*
* @author Sean Fong
*/
function SingleItem(props: SingleItemProps) {
const {
qItem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ interface GroupTableProps
groupCardElevation: number;
}

/**
* Main component to render a Group Table (gtable) Questionnaire item.
* @see {@link https://hl7.org/fhir/extensions/CodeSystem-questionnaire-item-control.html}
*
* @author Sean Fong
*/
function GroupTable(props: GroupTableProps) {
const {
qItem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
* limitations under the License.
*/

export * from './SingleItem';
export * from './RepeatItem';
export * from './RepeatGroup';
export * from './GridGroup';
export * from './Tables';
export * from './DateTimeItems';
export { SingleItem } from './SingleItem';
export { RepeatItem } from './RepeatItem';
export { RepeatGroup } from './RepeatGroup';
export { GroupTable } from './Tables';
export { GridGroup } from './GridGroup';
export { parseFhirDateToDisplayDate } from './DateTimeItems';
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ import { getQrItemsIndex, mapQItemsIndex } from '../../utils/mapItem';
import { updateQrItemsInGroup } from '../../utils/qrItem';
import type { QrRepeatGroup } from '../../interfaces/repeatGroup.interface';

/**
* Main component of the form-rendering engine.
* Renders the Questionnaire and QuestionnaireResponse defined in the state management stpres QuestionnaireStore and QuestionnaireResponseStore respectively.
* Use buildForm() in your wrapping component or in an event handler to initialise the form.
*
* @author Sean Fong
*/
function BaseRenderer() {
const sourceQuestionnaire = useQuestionnaireStore.use.sourceQuestionnaire();
const updateExpressions = useQuestionnaireStore.use.updateExpressions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import React from 'react';
import ThemeProvider from '../../theme/Theme';
import RendererThemeProvider from '../../theme/Theme';
import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
import useInitialiseRenderer from '../../hooks/useInitialiseRenderer';
import Box from '@mui/material/Box';
Expand All @@ -28,7 +28,19 @@ import useQueryClient from '../../hooks/useQueryClient';
import BaseRenderer from './BaseRenderer';
import type Client from 'fhirclient/lib/Client';

interface SmartFormsRendererProps {
/**
* SmartFormsRenderer properties
*
* @property questionnaire - Input FHIR R4 Questionnaire to be rendered
* @property questionnaireResponse - Pre-populated QuestionnaireResponse to be rendered (optional)
* @property additionalVariables - Additional key-value pair of SDC variables <name, variable extension> for testing (optional)
* @property terminologyServerUrl - Terminology server url to fetch terminology. If not provided, the default terminology server will be used. (optional)
* @property fhirClient - FHIRClient object to perform further FHIR calls. At the moment it's only used in answerExpressions (optional)
* @property readOnly - Applies read-only mode to all items in the form
*
* @author Sean Fong
*/
export interface SmartFormsRendererProps {
questionnaire: Questionnaire;
questionnaireResponse?: QuestionnaireResponse;
additionalVariables?: Record<string, object>;
Expand All @@ -37,6 +49,14 @@ interface SmartFormsRendererProps {
readOnly?: boolean;
}

// Will be deprecated in version 1.0.0. Use alternative() instead. //FIXME add alternative

/**
* A self-initialising wrapper around the BaseRenderer rendering engine.
* See SmartFormsRendererProps for props.
*
* @author Sean Fong
*/
function SmartFormsRenderer(props: SmartFormsRendererProps) {
const {
questionnaire,
Expand Down Expand Up @@ -67,11 +87,11 @@ function SmartFormsRenderer(props: SmartFormsRendererProps) {
}

return (
<ThemeProvider>
<RendererThemeProvider>
<QueryClientProvider client={queryClient}>
<BaseRenderer />
</QueryClientProvider>
</ThemeProvider>
</RendererThemeProvider>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
* limitations under the License.
*/

export type { SmartFormsRendererProps } from './SmartFormsRenderer';
export { default as SmartFormsRenderer } from './SmartFormsRenderer';
export { default as BaseRenderer } from './BaseRenderer';
12 changes: 10 additions & 2 deletions packages/smart-forms-renderer/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,13 @@
* limitations under the License.
*/

export * from './Renderer';
export * from './FormComponents';
export type { SmartFormsRendererProps } from './Renderer';
export { SmartFormsRenderer, BaseRenderer } from './Renderer';
export {
SingleItem,
RepeatItem,
RepeatGroup,
GroupTable,
GridGroup,
parseFhirDateToDisplayDate
} from './FormComponents';
1 change: 1 addition & 0 deletions packages/smart-forms-renderer/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as useHidden } from './useHidden';
export { default as useBuildForm } from './useBuildForm';
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ import { useLayoutEffect, useState } from 'react';
import { buildForm } from '../utils';
import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';

function useBuildFormForStorybook(
questionnaire: Questionnaire,
questionnaireResponse?: QuestionnaireResponse
) {
function useBuildForm(questionnaire: Questionnaire, questionnaireResponse?: QuestionnaireResponse) {
const [isBuilding, setIsBuilding] = useState(true);

useLayoutEffect(() => {
Expand All @@ -34,4 +31,4 @@ function useBuildFormForStorybook(
return isBuilding;
}

export default useBuildFormForStorybook;
export default useBuildForm;
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function useValueSetCodings(qItem: QuestionnaireItem): {
return [];
}, [cachedValueSetCodings, processedValueSetCodings, valueSetUrl]);

// Attempt to get codings from answer expression
const answerExpression = getAnswerExpression(qItem)?.expression;
initialCodings = useMemo(() => {
if (initialCodings.length === 0 && answerExpression) {
Expand Down
143 changes: 47 additions & 96 deletions packages/smart-forms-renderer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,47 @@
import { questionnaireResponseStore, questionnaireStore } from './stores';
import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
import { removeEmptyAnswers } from './utils/removeEmptyAnswers';
import type { ItemToRepopulate } from './utils/repopulateItems';
import { getItemsToRepopulate } from './utils/repopulateItems';
import { repopulateItemsIntoResponse } from './utils/repopulateIntoResponse';

export * from './components';
export * from './stores';
export * from './hooks';
export * from './utils';
export * from './interfaces';
export type { ItemToRepopulate };

/**
* Destroy the form to clean up the questionnaire and questionnaireResponse stores.
*
* @author Sean Fong
*/
export function destroyForm(): void {
questionnaireStore.getState().destroySourceQuestionnaire();
questionnaireResponseStore.getState().destroySourceResponse();
}

/**
* Get the filled QuestionnaireResponse at its current state.
* If no changes have been made to the form, the initial QuestionnaireResponse is returned.
*
* @author Sean Fong
*/
export function getResponse(): QuestionnaireResponse {
return questionnaireResponseStore.getState().updatableResponse;
}

/**
* Remove all hidden answers from the filled QuestionnaireResponse.
* This takes into account the questionnaire-hidden extension, enableWhens, enableWhenExpressions and empty strings.
*
* @author Sean Fong
*/
export function removeEmptyAnswersFromResponse(
questionnaire: Questionnaire,
questionnaireResponse: QuestionnaireResponse
): QuestionnaireResponse {
const enableWhenIsActivated = questionnaireStore.getState().enableWhenIsActivated;
const enableWhenItems = questionnaireStore.getState().enableWhenItems;
const enableWhenExpressions = questionnaireStore.getState().enableWhenExpressions;

return removeEmptyAnswers({
questionnaire,
questionnaireResponse,
enableWhenIsActivated,
enableWhenItems,
enableWhenExpressions
});
}

/**
* Compare latest data from the server with the current QuestionnaireResponse and decide items to re-populate.
*
* @author Sean Fong
*/
export function generateItemsToRepopulate(populatedResponse: QuestionnaireResponse) {
const sourceQuestionnaire = questionnaireStore.getState().sourceQuestionnaire;
const tabs = questionnaireStore.getState().tabs;
const updatableResponse = questionnaireResponseStore.getState().updatableResponse;
const enableWhenIsActivated = questionnaireStore.getState().enableWhenIsActivated;
const enableWhenItems = questionnaireStore.getState().enableWhenItems;
const enableWhenExpressions = questionnaireStore.getState().enableWhenExpressions;

return getItemsToRepopulate({
sourceQuestionnaire,
tabs,
populatedResponse,
updatableResponse,
enableWhenIsActivated,
enableWhenItems,
enableWhenExpressions
});
}

/**
* Re-populate checked items in the re-population dialog into the current QuestionnaireResponse.
*
* @author Sean Fong
*/
export function repopulateResponse(checkedItemsToRepopulate: Record<string, ItemToRepopulate>) {
const sourceQuestionnaire = questionnaireStore.getState().sourceQuestionnaire;
const updatableResponse = questionnaireResponseStore.getState().updatableResponse;

return repopulateItemsIntoResponse(
sourceQuestionnaire,
updatableResponse,
checkedItemsToRepopulate
);
}
// interface exports
export type { Tab, Tabs, Variables, VariableXFhirQuery, LaunchContext } from './interfaces';

// component exports
export type { SmartFormsRendererProps } from './components';
export {
SmartFormsRenderer,
BaseRenderer,
SingleItem,
RepeatItem,
RepeatGroup,
GroupTable,
GridGroup,
parseFhirDateToDisplayDate
} from './components';

// state management store exports
export {
questionnaireStore,
useQuestionnaireStore,
questionnaireResponseStore,
useQuestionnaireResponseStore,
smartConfigStore,
useSmartConfigStore,
terminologyServerStore,
useTerminologyServerStore
} from './stores';

// hooks exports
export { useHidden, useBuildForm } from './hooks';

// utils exports
export type { ItemToRepopulate } from './utils';
export {
buildForm,
destroyForm,
getResponse,
removeEmptyAnswersFromResponse,
isSpecificItemControl,
isRepeatItemAndNotCheckbox,
initialiseQuestionnaireResponse,
generateItemsToRepopulate,
repopulateResponse
} from './utils';

// theme provider exports
export { RendererThemeProvider } from './theme';
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
/*
* 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.
*/

/**
* CalculatedExpression interface
*
* @property expression - CalculatedExpression FHIRPath expression
* @property from - Whether the expressions is for the item itself or for item._text
* @property value - Evaluated value of the expression via FHIRPath
*/
export interface CalculatedExpression {
expression: string;
from: 'item' | 'item._text';
Expand Down
Loading

0 comments on commit 59ce39c

Please sign in to comment.