Skip to content

Commit

Permalink
Working answerExpression
Browse files Browse the repository at this point in the history
  • Loading branch information
fongsean committed Jun 19, 2024
1 parent 2828c76 commit afd8e8a
Show file tree
Hide file tree
Showing 16 changed files with 215 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function ChoiceRadioAnswerOptionItem(props: ChoiceRadioAnswerOptionItemProps) {
const { calcExpUpdated } = useCodingCalculatedExpression({
qItem: qItem,
valueInString: valueChoice ?? '',
onChangeByCalcExpressionString: (newValueString: string) => {
onChangeByCalcExpressionString: (newValueString: string | null) => {
handleChange(newValueString);
},
onChangeByCalcExpressionNull: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ function ChoiceRadioAnswerValueSetItem(props: ChoiceRadioAnswerValueSetItemProps
const { calcExpUpdated } = useCodingCalculatedExpression({
qItem: qItem,
valueInString: valueRadio ?? '',
onChangeByCalcExpressionString: (newValueString: string) => {
handleChange(newValueString);
onChangeByCalcExpressionString: (newValueString: string | null) => {
handleChange(newValueString ?? '');
},
onChangeByCalcExpressionNull: () => {
onQrItemChange(createEmptyQrItem(qItem));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ function ChoiceSelectAnswerOptionItem(props: ChoiceSelectAnswerOptionItemProps)
const { calcExpUpdated } = useCodingCalculatedExpression({
qItem: qItem,
valueInString: valueChoice ?? '',
onChangeByCalcExpressionString: (newValueString: string) => {
handleChange(newValueString);
onChangeByCalcExpressionString: (newValueString: string | null) => {
handleChange(newValueString ?? '');
},
onChangeByCalcExpressionNull: () => {
onQrItemChange(createEmptyQrItem(qItem));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ function ChoiceSelectAnswerValueSetFields(props: ChoiceSelectAnswerValueSetField
);
}

if (codings.length === 0) {
return (
<StyledAlert color="info">
<Typography variant="subtitle2">
There are no options available for {terminologyError.answerValueSet}
</Typography>
</StyledAlert>
);
}

// Fallback when something went wrong
return (
<StyledAlert color="error">
<ErrorOutlineIcon color="error" sx={{ pr: 0.75 }} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function ChoiceSelectAnswerValueSetItem(props: ChoiceSelectAnswerValueSetItemPro
}

// Get codings/options from valueSet
const { codings, isLoading, terminologyError } = useValueSetCodings(qItem, () => {
const { codings, isLoading, terminologyError, setCodings } = useValueSetCodings(qItem, () => {
onQrItemChange(createEmptyQrItem(qItem));
});

Expand All @@ -83,19 +83,41 @@ function ChoiceSelectAnswerValueSetItem(props: ChoiceSelectAnswerValueSetItemPro
[]
);

const answerOptions = useMemo(() => convertCodingsToAnswerOptions(codings), [codings]);

// Process calculated expressions
const { calcExpUpdated } = useCodingCalculatedExpression({
qItem: qItem,
valueInString: valueCoding?.code ?? '',
onChangeByCalcExpressionString: (newValueString) => {
if (codings.length > 0) {
const qrAnswer = findInAnswerOptions(answerOptions, newValueString);
onChangeByCalcExpressionString: (newValueString, newCodings) => {
// no new calcexp value and no newCodings
if (newValueString === null && newCodings.length === 0) {
return;
}

// no new calcexp value only
if (newValueString === null) {
setCodings(newCodings);
createEmptyQrItem(qItem);
return;
}

// no newCodings
if (newCodings.length === 0) {
const qrAnswer = findInAnswerOptions(codings, newValueString);
onQrItemChange(
qrAnswer ? { ...createEmptyQrItem(qItem), answer: [qrAnswer] } : createEmptyQrItem(qItem)
);
return;
}

// both is updated
const qrAnswer = findInAnswerOptions(
convertCodingsToAnswerOptions(newCodings),
newValueString
);
setCodings(newCodings);
onQrItemChange(
qrAnswer ? { ...createEmptyQrItem(qItem), answer: [qrAnswer] } : createEmptyQrItem(qItem)
);
},
onChangeByCalcExpressionNull: () => {
onQrItemChange(createEmptyQrItem(qItem));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,26 @@ function BaseRenderer() {
const readOnly = useQuestionnaireStore.use.readOnly();

const updatableResponse = useQuestionnaireResponseStore.use.updatableResponse();
const validateQuestionnaire = useQuestionnaireResponseStore.use.validateQuestionnaire();
const updateResponse = useQuestionnaireResponseStore.use.updateResponse();

const qItemsIndexMap = useMemo(() => mapQItemsIndex(sourceQuestionnaire), [sourceQuestionnaire]);

function handleTopLevelQRItemSingleChange(newTopLevelQRItem: QuestionnaireResponseItem) {
async function handleTopLevelQRItemSingleChange(newTopLevelQRItem: QuestionnaireResponseItem) {
const updatedResponse: QuestionnaireResponse = cloneDeep(updatableResponse);

updateQrItemsInGroup(newTopLevelQRItem, null, updatedResponse, qItemsIndexMap);

updateExpressions(updatedResponse);
validateQuestionnaire(sourceQuestionnaire, updatedResponse);
updateResponse(updatedResponse);
await updateExpressions(updatedResponse);
}

function handleTopLevelQRItemMultipleChange(newTopLevelQRItems: QrRepeatGroup) {
async function handleTopLevelQRItemMultipleChange(newTopLevelQRItems: QrRepeatGroup) {
const updatedResponse: QuestionnaireResponse = cloneDeep(updatableResponse);

updateQrItemsInGroup(null, newTopLevelQRItems, updatedResponse, qItemsIndexMap);

updateExpressions(updatedResponse);
validateQuestionnaire(sourceQuestionnaire, updatedResponse);
updateResponse(updatedResponse);
await updateExpressions(updatedResponse);
}

const topLevelQItems = sourceQuestionnaire.item;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
*/

import { useEffect, useState } from 'react';
import type { QuestionnaireItem } from 'fhir/r4';
import type { Coding, QuestionnaireItem } from 'fhir/r4';
import { useQuestionnaireStore } from '../stores';
import { AnswerExpression } from '../interfaces/answerExpression.interface';

interface UseCodingCalculatedExpression {
calcExpUpdated: boolean;
Expand All @@ -26,7 +27,7 @@ interface UseCodingCalculatedExpression {
interface UseCodingCalculatedExpressionProps {
qItem: QuestionnaireItem;
valueInString: string;
onChangeByCalcExpressionString: (newValueInString: string) => void;
onChangeByCalcExpressionString: (newValueInString: string | null, newCodings: Coding[]) => void;
onChangeByCalcExpressionNull: () => void;
}

Expand All @@ -38,50 +39,55 @@ function useCodingCalculatedExpression(
props;

const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
const answerExpressions = useQuestionnaireStore.use.answerExpressions();

const [calcExpUpdated, setCalcExpUpdated] = useState(false);

const answerExpression: AnswerExpression | null = answerExpressions[qItem.linkId] ?? null;
useEffect(
() => {
const calcExpression = calculatedExpressions[qItem.linkId]?.find(
(exp) => exp.from === 'item'
);

if (!calcExpression) {
return;
let newCodings: Coding[] = [];
if (Array.isArray(answerExpression.options)) {
newCodings = answerExpression.options as Coding[];
}

// only update if calculated value is different from current value
if (
calcExpression.value !== valueInString &&
(typeof calcExpression.value === 'string' ||
typeof calcExpression.value === 'number' ||
calcExpression.value === null)
) {
// update ui to show calculated value changes
setCalcExpUpdated(true);
setTimeout(() => {
setCalcExpUpdated(false);
}, 500);
let newValueString: string | null = null;
if (calcExpression) {
// only update if calculated value is different from current value
if (
calcExpression.value !== valueInString &&
(typeof calcExpression.value === 'string' ||
typeof calcExpression.value === 'number' ||
calcExpression.value === null)
) {
// update ui to show calculated value changes
setCalcExpUpdated(true);
setTimeout(() => {
setCalcExpUpdated(false);
}, 500);

// calculatedExpression value is null
if (calcExpression.value === null) {
onChangeByCalcExpressionNull();
return;
}

// calculatedExpression value is a string or number
const newValueString =
typeof calcExpression.value === 'string'
? calcExpression.value
: calcExpression.value.toString();
// calculatedExpression value is null
if (calcExpression.value === null) {
onChangeByCalcExpressionNull();
return;
}

onChangeByCalcExpressionString(newValueString);
// calculatedExpression value is a string or number
newValueString =
typeof calcExpression.value === 'string'
? calcExpression.value
: calcExpression.value.toString();
}
}
onChangeByCalcExpressionString(newValueString, newCodings);
},
// Only trigger this effect if calculatedExpression of item changes
// eslint-disable-next-line react-hooks/exhaustive-deps
[calculatedExpressions]
[calculatedExpressions, answerExpression.version]
);

return { calcExpUpdated: calcExpUpdated };
Expand Down
18 changes: 2 additions & 16 deletions packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function useValueSetCodings(
codings: Coding[];
isLoading: boolean;
terminologyError: TerminologyError;
setCodings: (codings: Coding[]) => void;
} {
const patient = useSmartConfigStore.use.patient();
const user = useSmartConfigStore.use.user();
Expand Down Expand Up @@ -163,10 +164,6 @@ function useValueSetCodings(
promise
.then(async (valueSet: ValueSet) => {
const codings = getValueSetCodings(valueSet);
if (qItem.linkId === 'scenario-3-associated-site') {
console.log('codings____');
console.log(codings);
}

addDisplayToCodingArray(codings, terminologyServerUrl)
.then((codingsWithDisplay) => {
Expand Down Expand Up @@ -196,18 +193,6 @@ function useValueSetCodings(
}
}, [qItem, dynamicValueSet?.version]);

// Instead of using dynamic answerValueSets, use answerExpressions
// useEffect(() => {
// const answerExpression = answerExpressions[qItem.linkId];
//
// if (!answerExpression) {
// return;
// }
//
// console.log('answerExpression____');
// console.log(answerExpression);
// }, [answerExpressions]);

// get options from answerValueSet on render
useEffect(() => {
const valueSetUrl = qItem.answerValueSet;
Expand Down Expand Up @@ -239,6 +224,7 @@ function useValueSetCodings(

return {
codings,
setCodings,
isLoading: loading,
terminologyError: { error: serverError, answerValueSet: valueSetUrl ?? '' }
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ import type { QuestionnaireItemAnswerOption } from 'fhir/r4';

export interface AnswerExpression {
expression: string;
value?: QuestionnaireItemAnswerOption;
version: number;
options?: QuestionnaireItemAnswerOption[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export interface QuestionnaireStoreType {
actionType: 'add' | 'remove'
) => void;
toggleEnableWhenActivation: (isActivated: boolean) => void;
updateExpressions: (updatedResponse: QuestionnaireResponse) => void;
updateExpressions: (updatedResponse: QuestionnaireResponse) => Promise<void>;
addCodingToCache: (valueSetUrl: string, codings: Coding[]) => void;
updatePopulatedProperties: (
populatedResponse: QuestionnaireResponse,
Expand Down Expand Up @@ -335,6 +335,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
updatedResponseItemMap,
enableWhenExpressions: get().enableWhenExpressions,
calculatedExpressions: get().calculatedExpressions,
answerExpressions: get().answerExpressions,
variablesFhirPath: get().variables.fhirPathVariables,
dynamicValueSets: get().dynamicValueSets,
existingFhirPathContext: get().fhirPathContext
Expand All @@ -347,7 +348,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
dynamicValueSets: updatedDynamicValueSets,
fhirPathContext: updatedFhirPathContext
}));
return 0;
return;
}

set(() => ({
Expand Down
Loading

0 comments on commit afd8e8a

Please sign in to comment.