Skip to content

Commit

Permalink
Add enableWhenAsReadOnly and flyover SDC UI override props in buildFo…
Browse files Browse the repository at this point in the history
…rm config
  • Loading branch information
fongsean committed Oct 31, 2024
1 parent 69c228b commit df60faa
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function GroupItemView(props: GroupItemViewProps) {
onQrRepeatGroupChange
} = props;

const readOnly = useReadOnly(qItem, parentIsReadOnly);
const readOnly = useReadOnly(qItem, parentIsReadOnly, parentRepeatGroupIndex);

// Render collapsible group item
// If group item is a repeating instance, do not render group item as collapsible
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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 React from 'react';
import Tooltip from '@mui/material/Tooltip';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { useQuestionnaireStore } from '../../../stores';

interface FlyoverItemProps {
displayFlyover: string;
}

function FlyoverItem(props: FlyoverItemProps) {
const { displayFlyover } = props;

const sdcUiOverrideComponents = useQuestionnaireStore.use.sdcUiOverrideComponents();
const FlyoverOverrideComponent = sdcUiOverrideComponents['flyover'];

// If a flyover override component is defined for this item, render it
if (FlyoverOverrideComponent && typeof FlyoverOverrideComponent === 'function') {
return <FlyoverOverrideComponent displayText={displayFlyover} />;
}

return (
<Tooltip
title={displayFlyover}
placement="top"
slotProps={{
popper: {
modifiers: [
{
name: 'offset',
options: {
offset: [0, -8]
}
}
]
}
}}>
<span>
<InfoOutlinedIcon sx={{ color: 'text.secondary' }} fontSize="small" />
</span>
</Tooltip>
);
}

export default FlyoverItem;
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ import ContextDisplayItem from './ContextDisplayItem';
import type { QuestionnaireItem } from 'fhir/r4';
import { getContextDisplays } from '../../../utils/tabs';
import ItemLabelText from './ItemLabelText';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import useRenderingExtensions from '../../../hooks/useRenderingExtensions';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import FlyoverItem from './FlyoverItem';

interface LabelWrapperProps {
qItem: QuestionnaireItem;
Expand All @@ -39,40 +38,20 @@ function ItemLabelWrapper(props: LabelWrapperProps) {

return (
<Box display="flex" alignItems="center" justifyContent="space-between">
<Tooltip
title={displayFlyover}
placement="top"
slotProps={{
popper: {
modifiers: [
{
name: 'offset',
options: {
offset: [0, -8]
}
}
]
}
}}>
<span>
<Box position="relative">
{required ? (
<Typography
color="red"
sx={{ position: 'absolute', top: 0, left: -8 }} // Adjust top and left values as needed
>
*
</Typography>
) : null}
<Box display="flex" columnGap={0.5} justifyContent="space-between" alignItems="center">
<ItemLabelText qItem={qItem} readOnly={readOnly} />
{displayFlyover !== '' ? (
<InfoOutlinedIcon sx={{ color: 'text.secondary' }} fontSize="small" />
) : null}
</Box>
</Box>
</span>
</Tooltip>
<Box position="relative">
{required ? (
<Typography
color="red"
sx={{ position: 'absolute', top: 0, left: -8 }} // Adjust top and left values as needed
>
*
</Typography>
) : null}
<Box display="flex" columnGap={0.75} justifyContent="space-between" alignItems="center">
<ItemLabelText qItem={qItem} readOnly={readOnly} />
{displayFlyover !== '' ? <FlyoverItem displayFlyover={displayFlyover} /> : null}
</Box>
</Box>

<Box display="flex" columnGap={0.5}>
{contextDisplayItems.map((item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function RepeatGroupItem(props: RepeatGroupItemProps) {
onQrItemChange
} = props;

const readOnly = useReadOnly(qItem, parentIsReadOnly);
const readOnly = useReadOnly(qItem, parentIsReadOnly, repeatGroupIndex);

return (
<RepeatGroupContainerStack direction="row" justifyContent="end">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function SingleItem(props: SingleItemProps) {
[qItem]
);

const readOnly = useReadOnly(qItem, parentIsReadOnly);
const readOnly = useReadOnly(qItem, parentIsReadOnly, parentRepeatGroupIndex);
const itemIsHidden = useHidden(qItem, parentRepeatGroupIndex);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ function SingleItemSwitcher(props: SingleItemSwitcherProps) {
const { qItem, qrItem, isRepeated, isTabled, showMinimalView, parentIsReadOnly, onQrItemChange } =
props;

const customComponents = useQuestionnaireStore.use.customComponents();
const CustomComponent = customComponents[qItem.linkId];
const qItemOverrideComponents = useQuestionnaireStore.use.qItemOverrideComponents();
const QItemOverrideComponent = qItemOverrideComponents[qItem.linkId];

// If a custom component is defined for this item, render it
// If a qItem override component is defined for this item, render it
// Don't get too strict with the checks for now
if (CustomComponent && typeof CustomComponent === 'function') {
if (QItemOverrideComponent && typeof QItemOverrideComponent === 'function') {
return (
<CustomComponent
<QItemOverrideComponent
qItem={qItem}
qrItem={qrItem}
isRepeated={isRepeated}
Expand All @@ -78,7 +78,7 @@ function SingleItemSwitcher(props: SingleItemSwitcherProps) {
// Otherwise, render the default form component based on the item type
switch (qItem.type) {
case 'display':
return <DisplayItem qItem={qItem} />;
return <DisplayItem qItem={qItem} parentIsReadOnly={parentIsReadOnly} />;
case 'boolean':
return (
<BooleanItem
Expand Down Expand Up @@ -238,7 +238,6 @@ function SingleItemSwitcher(props: SingleItemSwitcherProps) {
/>
);
case 'quantity':
// FIXME quantity item uses the same component as decimal item currently
return (
<QuantityItem
qItem={qItem}
Expand Down
20 changes: 13 additions & 7 deletions packages/smart-forms-renderer/src/hooks/useBuildForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { buildForm } from '../utils';
import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
import type { RendererStyling } from '../stores/rendererStylingStore';
import { useRendererStylingStore } from '../stores/rendererStylingStore';
import type { QItemOverrideComponentProps, SdcUiOverrideComponentProps } from '../interfaces';

/**
* React hook wrapping around the buildForm() function to build a form from a questionnaire and an optional QuestionnaireResponse.
Expand All @@ -32,7 +33,9 @@ import { useRendererStylingStore } from '../stores/rendererStylingStore';
* @param terminologyServerUrl - Terminology server url to fetch terminology. If not provided, the default terminology server will be used. (optional)
* @param additionalVariables - Additional key-value pair of SDC variables `Record<name, variable extension>` for testing (optional)
* @param rendererStylingOptions - Renderer styling to be applied to the form. See docs for styling options. (optional)
* @param customComponents - FIXME add comment
* @param qItemOverrideComponents - FIXME add comment
* @param sdcUiOverrideComponents - FIXME add comment
*
*
* @author Sean Fong
*/
Expand All @@ -43,7 +46,8 @@ function useBuildForm(
terminologyServerUrl?: string,
additionalVariables?: Record<string, object>,
rendererStylingOptions?: RendererStyling,
customComponents?: Record<string, ComponentType<any>>
qItemOverrideComponents?: Record<string, ComponentType<QItemOverrideComponentProps>>,
sdcUiOverrideComponents?: Record<string, ComponentType<SdcUiOverrideComponentProps>>
) {
const [isBuilding, setIsBuilding] = useState(true);

Expand All @@ -61,19 +65,21 @@ function useBuildForm(
readOnly,
terminologyServerUrl,
additionalVariables,
customComponents
qItemOverrideComponents,
sdcUiOverrideComponents
).then(() => {
setIsBuilding(false);
});
}, [
customComponents,
questionnaire,
questionnaireResponse,
readOnly,
rendererStylingOptions,
setRendererStyling,
terminologyServerUrl,
additionalVariables
additionalVariables,
rendererStylingOptions,
qItemOverrideComponents,
sdcUiOverrideComponents,
setRendererStyling
]);

return isBuilding;
Expand Down
12 changes: 12 additions & 0 deletions packages/smart-forms-renderer/src/hooks/useHidden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { QuestionnaireItem } from 'fhir/r4';
import { useQuestionnaireStore } from '../stores';
import { isHiddenByEnableWhen } from '../utils/qItem';
import { structuredDataCapture } from 'fhir-sdc-helpers';
import { useRendererStylingStore } from '../stores/rendererStylingStore';

/**
* React hook to determine if a QuestionnaireItem is hidden via item.hidden, enableWhens, enableWhenExpressions.
Expand All @@ -31,10 +32,21 @@ function useHidden(qItem: QuestionnaireItem, parentRepeatGroupIndex?: number): b
const enableWhenItems = useQuestionnaireStore.use.enableWhenItems();
const enableWhenExpressions = useQuestionnaireStore.use.enableWhenExpressions();

const enableWhenAsReadOnly = useRendererStylingStore.use.enableWhenAsReadOnly();

if (structuredDataCapture.getHidden(qItem)) {
return true;
}

// If enableWhenAsReadOnly is true, then items hidden by enableWhen should be displayed, but set as readOnly
// If enableWhenAsReadOnly is 'non-group', then items hidden by enableWhen should be displayed, but set as readOnly - only applies if item.type != group
if (
enableWhenAsReadOnly === true ||
(enableWhenAsReadOnly === 'non-group' && qItem.type !== 'group')
) {
return false;
}

return isHiddenByEnableWhen({
linkId: qItem.linkId,
enableWhenIsActivated,
Expand Down
33 changes: 32 additions & 1 deletion packages/smart-forms-renderer/src/hooks/useReadOnly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,40 @@

import type { QuestionnaireItem } from 'fhir/r4';
import useRenderingExtensions from './useRenderingExtensions';
import { useRendererStylingStore } from '../stores/rendererStylingStore';
import { isHiddenByEnableWhen } from '../utils/qItem';
import { useQuestionnaireStore } from '../stores';

function useReadOnly(qItem: QuestionnaireItem, parentIsReadOnly: boolean | undefined): boolean {
function useReadOnly(
qItem: QuestionnaireItem,
parentIsReadOnly: boolean | undefined,
parentRepeatGroupIndex?: number
): boolean {
let { readOnly } = useRenderingExtensions(qItem);

const enableWhenIsActivated = useQuestionnaireStore.use.enableWhenIsActivated();
const enableWhenItems = useQuestionnaireStore.use.enableWhenItems();
const enableWhenExpressions = useQuestionnaireStore.use.enableWhenExpressions();

const enableWhenAsReadOnly = useRendererStylingStore.use.enableWhenAsReadOnly();

// If enableWhenAsReadOnly is true, then items hidden by enableWhen should be displayed, but set as readOnly
// If enableWhenAsReadOnly is 'non-group', then items hidden by enableWhen should be displayed, but set as readOnly - only applies if item.type != group
if (!readOnly) {
if (
enableWhenAsReadOnly === true ||
(enableWhenAsReadOnly === 'non-group' && qItem.type !== 'group')
) {
readOnly = isHiddenByEnableWhen({
linkId: qItem.linkId,
enableWhenIsActivated,
enableWhenItems,
enableWhenExpressions,
parentRepeatGroupIndex
});
}
}

if (typeof parentIsReadOnly === 'boolean' && parentIsReadOnly) {
readOnly = parentIsReadOnly;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/smart-forms-renderer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export type {
Variables,
VariableXFhirQuery,
LaunchContext,
CustomComponentProps
QItemOverrideComponentProps,
SdcUiOverrideComponentProps
} from './interfaces';

// component exports
Expand Down
5 changes: 4 additions & 1 deletion packages/smart-forms-renderer/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ export type { Tab, Tabs } from './tab.interface';
export type { Variables, VariableXFhirQuery } from './variables.interface';
export type { LaunchContext } from './populate.interface';
export type { EnableWhenItems, EnableWhenExpressions } from './enableWhen.interface';
export type { CustomComponentProps } from './customComponent.interface';
export type {
QItemOverrideComponentProps,
SdcUiOverrideComponentProps
} from './overrideComponent.interface';
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
* limitations under the License.
*/

import { QuestionnaireItem, type QuestionnaireResponseItem } from 'fhir/r4';
import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';

export interface CustomComponentProps {
export interface QItemOverrideComponentProps {
qItem: QuestionnaireItem;
qrItem: QuestionnaireResponseItem | null;
isRepeated: boolean;
isTabled: boolean;
parentIsReadOnly?: boolean;
onQrItemChange: (qrItem: QuestionnaireResponseItem) => unknown;
}

export interface SdcUiOverrideComponentProps {
displayText: string;
}
Loading

0 comments on commit df60faa

Please sign in to comment.