From fc0980780a93fec8ee2a656c902cf0ffbe58f4cb Mon Sep 17 00:00:00 2001 From: qscgyjqscgyj Date: Fri, 29 Nov 2024 15:32:30 +0100 Subject: [PATCH 1/9] Added isItemEnabled util to use it for enable when schema --- .../enableWhen/equalOperator.test.ts | 120 ++++++++++++++++++ src/utils/__tests__/enableWhen/utils.ts | 70 ++++++++++ src/utils/enableWhen.ts | 65 ++++++++++ src/utils/questionnaire.ts | 22 ++-- 4 files changed, 266 insertions(+), 11 deletions(-) create mode 100644 src/utils/__tests__/enableWhen/equalOperator.test.ts create mode 100644 src/utils/__tests__/enableWhen/utils.ts create mode 100644 src/utils/enableWhen.ts diff --git a/src/utils/__tests__/enableWhen/equalOperator.test.ts b/src/utils/__tests__/enableWhen/equalOperator.test.ts new file mode 100644 index 00000000..adc63008 --- /dev/null +++ b/src/utils/__tests__/enableWhen/equalOperator.test.ts @@ -0,0 +1,120 @@ +import { isItemEnabled } from 'src/utils/enableWhen'; + +import { generateQAndQRData, QuestionnaireData } from './utils'; + +const ENABLE_WHEN_EQUAL_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + operator: '=', + conditionValue: { integer: 1 }, + answerValue: { integer: 1 }, + }), + enabled: true, + }, + { + ...generateQAndQRData({ + type: 'integer', + operator: '=', + conditionValue: { integer: 1 }, + answerValue: { integer: 2 }, + }), + enabled: false, + }, + { + ...generateQAndQRData({ + type: 'string', + operator: '=', + conditionValue: { string: 'test' }, + answerValue: { string: 'test' }, + }), + enabled: true, + }, + { + ...generateQAndQRData({ + type: 'string', + operator: '=', + conditionValue: { string: 'test' }, + answerValue: { string: 'test1' }, + }), + enabled: false, + }, + { + ...generateQAndQRData({ + type: 'choice', + operator: '=', + conditionValue: { + Coding: { + system: 'http://loinc.org', + code: '12345', + display: 'Test', + }, + }, + answerValue: { + Coding: { + system: 'http://loinc.org', + code: '12345', + display: 'Test', + }, + }, + }), + enabled: true, + }, + { + ...generateQAndQRData({ + type: 'choice', + operator: '=', + conditionValue: { + Coding: { + system: 'http://loinc.org', + code: '12345', + display: 'Test', + }, + }, + answerValue: { + Coding: { + system: 'http://loinc123123.org', + code: '12345', + display: 'Test', + }, + }, + }), + enabled: false, + }, + { + ...generateQAndQRData({ + type: 'boolean', + operator: '=', + conditionValue: { boolean: true }, + answerValue: { boolean: true }, + }), + enabled: true, + }, + { + ...generateQAndQRData({ + type: 'boolean', + operator: '=', + conditionValue: { boolean: true }, + answerValue: { boolean: false }, + }), + enabled: false, + }, +]; + +describe('Enable when: "="', () => { + test.each(ENABLE_WHEN_EQUAL_QUESTIONAIRES)( + 'should check if enabledLinkId is enabled or not', + async (questionnaireData) => { + const answerOptionArray = questionnaireData!.questionnaireResponse!.item![0]?.answer; + const conditionValue = questionnaireData!.questionnaire!.item![1]?.enableWhen![0]?.answer; + + const enabledResult = isItemEnabled({ + answerOptionArray, + answer: conditionValue, + operator: '=', + }); + + expect(enabledResult).toBe(questionnaireData.enabled); + }, + ); +}); diff --git a/src/utils/__tests__/enableWhen/utils.ts b/src/utils/__tests__/enableWhen/utils.ts new file mode 100644 index 00000000..e221e801 --- /dev/null +++ b/src/utils/__tests__/enableWhen/utils.ts @@ -0,0 +1,70 @@ +import { + Questionnaire, + QuestionnaireItem, + QuestionnaireItemEnableWhenAnswer, + QuestionnaireResponse, +} from '@beda.software/aidbox-types'; + +import { EnableWhenOperator } from 'src/utils/enableWhen'; + +export type QuestionnaireData = { + questionnaire: Questionnaire; + questionnaireResponse: QuestionnaireResponse; + enabled: boolean; +}; + +export const ITEM_TO_CKECK_LINK_ID = 'item-to-check'; +export const CONTROL_ITEM_LINK_ID = 'control-item'; + +interface GenerateQAndQRDataProps { + type: QuestionnaireItem['type']; + operator: EnableWhenOperator; + conditionValue: QuestionnaireItemEnableWhenAnswer; + answerValue: QuestionnaireItemEnableWhenAnswer; +} + +export function generateQAndQRData( + props: GenerateQAndQRDataProps, +): Pick { + const { type, operator, conditionValue, answerValue } = props; + + return { + questionnaire: { + resourceType: 'Questionnaire', + id: 'questionnaire', + title: 'Questionnaire', + status: 'active', + item: [ + { + linkId: ITEM_TO_CKECK_LINK_ID, + type: type, + text: 'Item to check', + required: false, + }, + { + linkId: CONTROL_ITEM_LINK_ID, + type: type, + text: 'Control item', + required: true, + enableWhen: [ + { + question: ITEM_TO_CKECK_LINK_ID, + operator, + answer: conditionValue, + }, + ], + }, + ], + }, + questionnaireResponse: { + resourceType: 'QuestionnaireResponse', + status: 'completed', + item: [ + { + linkId: ITEM_TO_CKECK_LINK_ID, + answer: [{ value: answerValue }], + }, + ], + }, + }; +} diff --git a/src/utils/enableWhen.ts b/src/utils/enableWhen.ts new file mode 100644 index 00000000..eed6be40 --- /dev/null +++ b/src/utils/enableWhen.ts @@ -0,0 +1,65 @@ +import _ from 'lodash'; +import type { QuestionnaireItemEnableWhenAnswer, QuestionnaireItemAnswerOption } from 'shared/src/contrib/aidbox'; + +type EnableWhenAnswerTypes = 'Coding' | 'string' | 'integer' | 'boolean'; +export type EnableWhenOperator = 'exists' | '=' | '!=' | '>' | '<' | '>=' | '<='; +type EnableWhenAnswerTypesMap = Record; + +export type EnableWhenValueType = string | number | boolean; + +const VALUES_TYPES: (keyof EnableWhenAnswerTypesMap)[] = ['Coding', 'string', 'integer', 'boolean']; +const VALUES_TYPES_PATH_MAP: EnableWhenAnswerTypesMap = { + Coding: { path: 'Coding' }, + string: { path: 'string' }, + integer: { path: 'integer' }, + boolean: { path: 'boolean' }, +}; + +const ENABLE_WHEN_OPERATORS_MAP: Record< + EnableWhenOperator, + (a: EnableWhenValueType, b: EnableWhenValueType) => boolean +> = { + exists: (a, b) => !!a === b, + '=': (a, b) => _.isEqual(a, b), + '!=': (a, b) => !_.isEqual(a, b), + '>': (a, b) => a > b, + '<': (a, b) => a < b, + '>=': (a, b) => a >= b, + '<=': (a, b) => a <= b, +}; + +function isOperatorValid(operator: string): operator is EnableWhenOperator { + return Object.keys(ENABLE_WHEN_OPERATORS_MAP).includes(operator); +} + +interface isItemEnabledProps { + answerOptionArray: QuestionnaireItemAnswerOption[] | undefined; + answer: QuestionnaireItemEnableWhenAnswer | undefined; + operator: string; +} +export function isItemEnabled(props: isItemEnabledProps): boolean { + const { answerOptionArray, answer, operator } = props; + + if (!isOperatorValid(operator)) { + return false; + } + + if (!answerOptionArray || !answer) { + return false; + } + + for (const valueTypePathKey of VALUES_TYPES) { + const path = VALUES_TYPES_PATH_MAP[valueTypePathKey].path; + + const value = _.get(answer, path); + + if (value) { + return answerOptionArray.some((answerOption) => { + const answerOptionValue = _.get(answerOption.value, path); + return ENABLE_WHEN_OPERATORS_MAP[operator](value, answerOptionValue); + }); + } + } + + return false; +} diff --git a/src/utils/questionnaire.ts b/src/utils/questionnaire.ts index bbe36ad3..884a0f3d 100644 --- a/src/utils/questionnaire.ts +++ b/src/utils/questionnaire.ts @@ -13,6 +13,7 @@ import { import { parseFHIRTime } from '@beda.software/fhir-react'; import { formatHumanDate, formatHumanDateTime } from './date'; +import { EnableWhenValueType, isItemEnabled } from './enableWhen'; import { evaluate } from './fhirpath'; export function getDisplay( @@ -116,17 +117,16 @@ export function questionnaireItemsToValidationSchema(questionnaireItems: Questio item.enableWhen.forEach((itemEnableWhen) => { const { question, operator, answer } = itemEnableWhen; // TODO: handle all other operators - if (operator === '=') { - validationSchema[item.linkId] = yup.mixed().when(question, { - is: (answerOptionArray: QuestionnaireItemAnswerOption[]) => - answerOptionArray && - answerOptionArray.some( - (answerOption) => answerOption?.value?.Coding?.code === answer?.Coding?.code, - ), - then: () => schema, - otherwise: () => yup.mixed().nullable(), - }); - } + validationSchema[item.linkId] = yup.mixed().when(question, { + is: (answerOptionArray: QuestionnaireItemAnswerOption[]) => + isItemEnabled({ + answerOptionArray, + answer, + operator, + }), + then: () => schema, + otherwise: () => yup.mixed().nullable(), + }); }); } else { validationSchema[item.linkId] = schema; From c7c4358fe5ed34b289f2a738c69b9334eb3ccd8b Mon Sep 17 00:00:00 2001 From: qscgyjqscgyj Date: Tue, 3 Dec 2024 14:20:26 +0100 Subject: [PATCH 2/9] Added full enableWhen yup schema support Added tests for each enableWhen operator cases --- src/utils/__tests__/enableWhen/equal.test.ts | 152 ++++++++++++++++++ .../enableWhen/equalOperator.test.ts | 120 -------------- src/utils/__tests__/enableWhen/exists.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/gt.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/gte.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/lt.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/lte.test.ts | 152 ++++++++++++++++++ .../__tests__/enableWhen/notEqual.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/utils.ts | 67 +++++--- src/utils/enableWhen.ts | 119 +++++++++++--- src/utils/questionnaire.ts | 20 +-- 11 files changed, 1211 insertions(+), 179 deletions(-) create mode 100644 src/utils/__tests__/enableWhen/equal.test.ts delete mode 100644 src/utils/__tests__/enableWhen/equalOperator.test.ts create mode 100644 src/utils/__tests__/enableWhen/exists.test.ts create mode 100644 src/utils/__tests__/enableWhen/gt.test.ts create mode 100644 src/utils/__tests__/enableWhen/gte.test.ts create mode 100644 src/utils/__tests__/enableWhen/lt.test.ts create mode 100644 src/utils/__tests__/enableWhen/lte.test.ts create mode 100644 src/utils/__tests__/enableWhen/notEqual.test.ts diff --git a/src/utils/__tests__/enableWhen/equal.test.ts b/src/utils/__tests__/enableWhen/equal.test.ts new file mode 100644 index 00000000..84b9e118 --- /dev/null +++ b/src/utils/__tests__/enableWhen/equal.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_EQUAL_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { integer: 1 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { string: 'test' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { string: 'test1' }, + }, + { + question: 'q2', + operator: '=', + answer: { string: 'test2' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test1' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { string: 'test1' }, + }, + { + question: 'q2', + operator: '=', + answer: { string: 'test2' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'asd' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { Coding: { code: 'test1', display: 'test1' } }, + }, + { + question: 'q2', + operator: '=', + answer: { Coding: { code: 'test2', display: 'test2' } }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { Coding: { code: 'asd', display: 'asd' } } }], + }, + { + linkId: 'q2', + answer: [{ value: { Coding: { code: 'test2', display: 'test2' } } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "="', () => { + test.each(ENABLE_WHEN_EQUAL_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/equalOperator.test.ts b/src/utils/__tests__/enableWhen/equalOperator.test.ts deleted file mode 100644 index adc63008..00000000 --- a/src/utils/__tests__/enableWhen/equalOperator.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { isItemEnabled } from 'src/utils/enableWhen'; - -import { generateQAndQRData, QuestionnaireData } from './utils'; - -const ENABLE_WHEN_EQUAL_QUESTIONAIRES: QuestionnaireData[] = [ - { - ...generateQAndQRData({ - type: 'integer', - operator: '=', - conditionValue: { integer: 1 }, - answerValue: { integer: 1 }, - }), - enabled: true, - }, - { - ...generateQAndQRData({ - type: 'integer', - operator: '=', - conditionValue: { integer: 1 }, - answerValue: { integer: 2 }, - }), - enabled: false, - }, - { - ...generateQAndQRData({ - type: 'string', - operator: '=', - conditionValue: { string: 'test' }, - answerValue: { string: 'test' }, - }), - enabled: true, - }, - { - ...generateQAndQRData({ - type: 'string', - operator: '=', - conditionValue: { string: 'test' }, - answerValue: { string: 'test1' }, - }), - enabled: false, - }, - { - ...generateQAndQRData({ - type: 'choice', - operator: '=', - conditionValue: { - Coding: { - system: 'http://loinc.org', - code: '12345', - display: 'Test', - }, - }, - answerValue: { - Coding: { - system: 'http://loinc.org', - code: '12345', - display: 'Test', - }, - }, - }), - enabled: true, - }, - { - ...generateQAndQRData({ - type: 'choice', - operator: '=', - conditionValue: { - Coding: { - system: 'http://loinc.org', - code: '12345', - display: 'Test', - }, - }, - answerValue: { - Coding: { - system: 'http://loinc123123.org', - code: '12345', - display: 'Test', - }, - }, - }), - enabled: false, - }, - { - ...generateQAndQRData({ - type: 'boolean', - operator: '=', - conditionValue: { boolean: true }, - answerValue: { boolean: true }, - }), - enabled: true, - }, - { - ...generateQAndQRData({ - type: 'boolean', - operator: '=', - conditionValue: { boolean: true }, - answerValue: { boolean: false }, - }), - enabled: false, - }, -]; - -describe('Enable when: "="', () => { - test.each(ENABLE_WHEN_EQUAL_QUESTIONAIRES)( - 'should check if enabledLinkId is enabled or not', - async (questionnaireData) => { - const answerOptionArray = questionnaireData!.questionnaireResponse!.item![0]?.answer; - const conditionValue = questionnaireData!.questionnaire!.item![1]?.enableWhen![0]?.answer; - - const enabledResult = isItemEnabled({ - answerOptionArray, - answer: conditionValue, - operator: '=', - }); - - expect(enabledResult).toBe(questionnaireData.enabled); - }, - ); -}); diff --git a/src/utils/__tests__/enableWhen/exists.test.ts b/src/utils/__tests__/enableWhen/exists.test.ts new file mode 100644 index 00000000..c2940bcc --- /dev/null +++ b/src/utils/__tests__/enableWhen/exists.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_EXISTS_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: true }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test1' } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: false }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: true }, + }, + { + question: 'q2', + operator: 'exists', + answer: { boolean: true }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test1' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: true }, + }, + { + question: 'q2', + operator: 'exists', + answer: { boolean: true }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: true }, + }, + { + question: 'q2', + operator: 'exists', + answer: { boolean: false }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { Coding: { code: 'asd', display: 'asd' } } }], + }, + { + linkId: 'q2', + answer: [{ value: { Coding: { code: 'test2', display: 'test2' } } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "exists"', () => { + test.each(ENABLE_WHEN_EXISTS_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/gt.test.ts b/src/utils/__tests__/enableWhen/gt.test.ts new file mode 100644 index 00000000..8df46d3c --- /dev/null +++ b/src/utils/__tests__/enableWhen/gt.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_GT_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 11 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 15 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 6 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 11 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 6 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: ">"', () => { + test.each(ENABLE_WHEN_GT_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/gte.test.ts b/src/utils/__tests__/enableWhen/gte.test.ts new file mode 100644 index 00000000..8ff81bdc --- /dev/null +++ b/src/utils/__tests__/enableWhen/gte.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_GTE_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 9 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 9 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: ">="', () => { + test.each(ENABLE_WHEN_GTE_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/lt.test.ts b/src/utils/__tests__/enableWhen/lt.test.ts new file mode 100644 index 00000000..aaaad297 --- /dev/null +++ b/src/utils/__tests__/enableWhen/lt.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_LT_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 4 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 4 } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 4 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "<"', () => { + test.each(ENABLE_WHEN_LT_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/lte.test.ts b/src/utils/__tests__/enableWhen/lte.test.ts new file mode 100644 index 00000000..eddd54e8 --- /dev/null +++ b/src/utils/__tests__/enableWhen/lte.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_LTE_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 11 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 0 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 11 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "<="', () => { + test.each(ENABLE_WHEN_LTE_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/notEqual.test.ts b/src/utils/__tests__/enableWhen/notEqual.test.ts new file mode 100644 index 00000000..b9feffe1 --- /dev/null +++ b/src/utils/__tests__/enableWhen/notEqual.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_NOT_EQUAL_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { integer: 1 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { string: 'test' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { string: 'test1' }, + }, + { + question: 'q2', + operator: '!=', + answer: { string: 'test2' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test1' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { string: 'test1' }, + }, + { + question: 'q2', + operator: '!=', + answer: { string: 'test2' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'asd' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { Coding: { code: 'test1', display: 'test1' } }, + }, + { + question: 'q2', + operator: '!=', + answer: { Coding: { code: 'test2', display: 'test2' } }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { Coding: { code: 'asd', display: 'asd' } } }], + }, + { + linkId: 'q2', + answer: [{ value: { Coding: { code: 'test2', display: 'test2' } } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "!="', () => { + test.each(ENABLE_WHEN_NOT_EQUAL_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/utils.ts b/src/utils/__tests__/enableWhen/utils.ts index e221e801..238eecae 100644 --- a/src/utils/__tests__/enableWhen/utils.ts +++ b/src/utils/__tests__/enableWhen/utils.ts @@ -1,32 +1,30 @@ import { Questionnaire, QuestionnaireItem, - QuestionnaireItemEnableWhenAnswer, + QuestionnaireItemEnableWhen, QuestionnaireResponse, + QuestionnaireResponseItem, } from '@beda.software/aidbox-types'; -import { EnableWhenOperator } from 'src/utils/enableWhen'; +import { evaluate, questionnaireItemsToValidationSchema } from 'src/utils'; export type QuestionnaireData = { questionnaire: Questionnaire; questionnaireResponse: QuestionnaireResponse; - enabled: boolean; }; -export const ITEM_TO_CKECK_LINK_ID = 'item-to-check'; export const CONTROL_ITEM_LINK_ID = 'control-item'; interface GenerateQAndQRDataProps { type: QuestionnaireItem['type']; - operator: EnableWhenOperator; - conditionValue: QuestionnaireItemEnableWhenAnswer; - answerValue: QuestionnaireItemEnableWhenAnswer; + enableWhen: QuestionnaireItemEnableWhen[]; + enableBehavior?: QuestionnaireItem['enableBehavior']; + qrItem: QuestionnaireResponseItem[]; } - export function generateQAndQRData( props: GenerateQAndQRDataProps, ): Pick { - const { type, operator, conditionValue, answerValue } = props; + const { type, enableWhen, enableBehavior, qrItem } = props; return { questionnaire: { @@ -36,9 +34,15 @@ export function generateQAndQRData( status: 'active', item: [ { - linkId: ITEM_TO_CKECK_LINK_ID, + linkId: 'q1', + type: type, + text: 'Item to check 1', + required: false, + }, + { + linkId: 'q2', type: type, - text: 'Item to check', + text: 'Item to check 2', required: false, }, { @@ -46,25 +50,40 @@ export function generateQAndQRData( type: type, text: 'Control item', required: true, - enableWhen: [ - { - question: ITEM_TO_CKECK_LINK_ID, - operator, - answer: conditionValue, - }, - ], + enableWhen, + enableBehavior, }, ], }, questionnaireResponse: { resourceType: 'QuestionnaireResponse', status: 'completed', - item: [ - { - linkId: ITEM_TO_CKECK_LINK_ID, - answer: [{ value: answerValue }], - }, - ], + item: qrItem, }, }; } + +export const ENABLE_WHEN_TESTS_TITLE = 'Should check if CONTROL_ITEM_LINK_ID is required or not'; + +export async function testEnableWhenCases(questionnaireData: QuestionnaireData) { + const { questionnaire, questionnaireResponse } = questionnaireData; + + const qrValues: QuestionnaireResponseItem[] = evaluate(questionnaireResponse, `item`); + const values = qrValues.reduce( + (acc, item) => { + acc[item.linkId] = item.answer; + return acc; + }, + {} as Record, + ); + const schema = questionnaireItemsToValidationSchema(questionnaire.item!); + + // NOTE: A way to debug a schema errors + // try { + // schema.validateSync(values); + // } catch (e) { + // console.log('Test schema valiadtion errors:', e); + // } + + expect(schema.isValidSync(values)).toBeTruthy(); +} diff --git a/src/utils/enableWhen.ts b/src/utils/enableWhen.ts index eed6be40..ceea58c3 100644 --- a/src/utils/enableWhen.ts +++ b/src/utils/enableWhen.ts @@ -1,11 +1,15 @@ import _ from 'lodash'; -import type { QuestionnaireItemEnableWhenAnswer, QuestionnaireItemAnswerOption } from 'shared/src/contrib/aidbox'; +import type { + QuestionnaireItemEnableWhenAnswer, + QuestionnaireItemAnswerOption, + QuestionnaireItemEnableWhen, +} from 'shared/src/contrib/aidbox'; +import * as yup from 'yup'; type EnableWhenAnswerTypes = 'Coding' | 'string' | 'integer' | 'boolean'; export type EnableWhenOperator = 'exists' | '=' | '!=' | '>' | '<' | '>=' | '<='; -type EnableWhenAnswerTypesMap = Record; - export type EnableWhenValueType = string | number | boolean; +type EnableWhenAnswerTypesMap = Record; const VALUES_TYPES: (keyof EnableWhenAnswerTypesMap)[] = ['Coding', 'string', 'integer', 'boolean']; const VALUES_TYPES_PATH_MAP: EnableWhenAnswerTypesMap = { @@ -19,47 +23,120 @@ const ENABLE_WHEN_OPERATORS_MAP: Record< EnableWhenOperator, (a: EnableWhenValueType, b: EnableWhenValueType) => boolean > = { - exists: (a, b) => !!a === b, + exists: (a, b) => !!a === !!b, '=': (a, b) => _.isEqual(a, b), '!=': (a, b) => !_.isEqual(a, b), - '>': (a, b) => a > b, - '<': (a, b) => a < b, - '>=': (a, b) => a >= b, - '<=': (a, b) => a <= b, + '>': (a, b) => b > a, + '<': (a, b) => b < a, + '>=': (a, b) => b >= a, + '<=': (a, b) => b <= a, }; function isOperatorValid(operator: string): operator is EnableWhenOperator { return Object.keys(ENABLE_WHEN_OPERATORS_MAP).includes(operator); } -interface isItemEnabledProps { +function getValueBYType(value: (QuestionnaireItemAnswerOption | undefined) | QuestionnaireItemEnableWhenAnswer) { + if (!value) { + return null; + } + + for (const valueTypePathKey of VALUES_TYPES) { + const path = VALUES_TYPES_PATH_MAP[valueTypePathKey].path; + + const valueResult = _.get(value, path) as EnableWhenAnswerTypes; + if (valueResult) { + return valueResult; + } + } + + return null; +} + +interface IsEnableWhenItemSucceedProps { answerOptionArray: QuestionnaireItemAnswerOption[] | undefined; answer: QuestionnaireItemEnableWhenAnswer | undefined; operator: string; } -export function isItemEnabled(props: isItemEnabledProps): boolean { +function isEnableWhenItemSucceed(props: IsEnableWhenItemSucceedProps): boolean { const { answerOptionArray, answer, operator } = props; if (!isOperatorValid(operator)) { return false; } - if (!answerOptionArray || !answer) { + if (!answerOptionArray || answerOptionArray.length === 0 || !answer) { return false; } - for (const valueTypePathKey of VALUES_TYPES) { - const path = VALUES_TYPES_PATH_MAP[valueTypePathKey].path; + const value = getValueBYType(answer); - const value = _.get(answer, path); + if (!value) { + return false; + } - if (value) { - return answerOptionArray.some((answerOption) => { - const answerOptionValue = _.get(answerOption.value, path); - return ENABLE_WHEN_OPERATORS_MAP[operator](value, answerOptionValue); - }); + return answerOptionArray.some((answerOption) => { + const answerOptionValue = getValueBYType(answerOption.value); + + if (answerOptionValue) { + return ENABLE_WHEN_OPERATORS_MAP[operator](value, answerOptionValue); } - } - return false; + return false; + }); +} + +interface GetEnableWhenItemSchemaProps extends GetQuestionItemEnableWhenSchemaProps { + currentIndex: number; + prevConditionResults?: boolean[]; +} +function getEnableWhenItemsSchema(props: GetEnableWhenItemSchemaProps) { + const { enableWhenItems, enableBehavior, currentIndex, schema, prevConditionResults } = props; + + const { question, operator, answer } = enableWhenItems[currentIndex]!; + + const isLastItem = currentIndex === enableWhenItems.length - 1; + + const conditionResults = prevConditionResults ? [...prevConditionResults] : []; + return yup.mixed().when(question, { + is: (answerOptionArray: QuestionnaireItemAnswerOption[]) => { + const isConditionSatisfied = isEnableWhenItemSucceed({ + answerOptionArray, + answer, + operator, + }); + + if (!enableBehavior || enableBehavior === 'all') { + return isConditionSatisfied; + } + + conditionResults.push(isConditionSatisfied); + + if (isLastItem) { + return conditionResults.some((result) => result); + } + + return true; + }, + then: () => + !isLastItem + ? getEnableWhenItemsSchema({ + enableWhenItems, + currentIndex: currentIndex + 1, + schema, + enableBehavior, + prevConditionResults: [...conditionResults], + }) + : schema, + otherwise: () => yup.mixed().nullable(), + }); +} + +interface GetQuestionItemEnableWhenSchemaProps { + enableWhenItems: QuestionnaireItemEnableWhen[]; + enableBehavior: string | undefined; + schema: yup.AnySchema; +} +export function getQuestionItemEnableWhenSchema(props: GetQuestionItemEnableWhenSchemaProps) { + return getEnableWhenItemsSchema({ ...props, currentIndex: 0 }); } diff --git a/src/utils/questionnaire.ts b/src/utils/questionnaire.ts index 884a0f3d..b3536a33 100644 --- a/src/utils/questionnaire.ts +++ b/src/utils/questionnaire.ts @@ -13,7 +13,7 @@ import { import { parseFHIRTime } from '@beda.software/fhir-react'; import { formatHumanDate, formatHumanDateTime } from './date'; -import { EnableWhenValueType, isItemEnabled } from './enableWhen'; +import { getQuestionItemEnableWhenSchema } from './enableWhen'; import { evaluate } from './fhirpath'; export function getDisplay( @@ -113,20 +113,12 @@ export function questionnaireItemsToValidationSchema(questionnaireItems: Questio } else { schema = item.required ? yup.mixed().required() : yup.mixed().nullable(); } + if (item.enableWhen) { - item.enableWhen.forEach((itemEnableWhen) => { - const { question, operator, answer } = itemEnableWhen; - // TODO: handle all other operators - validationSchema[item.linkId] = yup.mixed().when(question, { - is: (answerOptionArray: QuestionnaireItemAnswerOption[]) => - isItemEnabled({ - answerOptionArray, - answer, - operator, - }), - then: () => schema, - otherwise: () => yup.mixed().nullable(), - }); + validationSchema[item.linkId] = getQuestionItemEnableWhenSchema({ + enableWhenItems: item.enableWhen, + enableBehavior: item.enableBehavior, + schema, }); } else { validationSchema[item.linkId] = schema; From d729d8759b5c153dfc8e1ed6370fca9d03777337 Mon Sep 17 00:00:00 2001 From: qscgyjqscgyj Date: Tue, 3 Dec 2024 14:25:18 +0100 Subject: [PATCH 3/9] Fixed recursive schema function returning typee Ref #391 --- src/utils/enableWhen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/enableWhen.ts b/src/utils/enableWhen.ts index ceea58c3..17fe0149 100644 --- a/src/utils/enableWhen.ts +++ b/src/utils/enableWhen.ts @@ -90,7 +90,7 @@ interface GetEnableWhenItemSchemaProps extends GetQuestionItemEnableWhenSchemaPr currentIndex: number; prevConditionResults?: boolean[]; } -function getEnableWhenItemsSchema(props: GetEnableWhenItemSchemaProps) { +function getEnableWhenItemsSchema(props: GetEnableWhenItemSchemaProps): yup.AnySchema { const { enableWhenItems, enableBehavior, currentIndex, schema, prevConditionResults } = props; const { question, operator, answer } = enableWhenItems[currentIndex]!; From d77869f80f506fa30d4aabeb571cf248d041d328 Mon Sep 17 00:00:00 2001 From: Ilya Beda Date: Mon, 9 Dec 2024 13:58:53 +1100 Subject: [PATCH 4/9] Update demo smart apps type --- .../seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c1.yaml | 2 +- .../seeds/Client/de26b280-d3fc-4db1-9df3-50f3f328b7a5.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c1.yaml b/resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c1.yaml index 11a11eb9..d5ec00ed 100644 --- a/resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c1.yaml +++ b/resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c1.yaml @@ -4,7 +4,7 @@ auth: refresh_token: true secret_required: false access_token_expiration: 36000 -type: smart-on-fhir +type: smart-on-fhir-practitioner smart: launch_uri: https://www.smartforms.io/launch name: SmartForms diff --git a/resources/seeds/Client/de26b280-d3fc-4db1-9df3-50f3f328b7a5.yaml b/resources/seeds/Client/de26b280-d3fc-4db1-9df3-50f3f328b7a5.yaml index b8723856..7a8e6c0d 100644 --- a/resources/seeds/Client/de26b280-d3fc-4db1-9df3-50f3f328b7a5.yaml +++ b/resources/seeds/Client/de26b280-d3fc-4db1-9df3-50f3f328b7a5.yaml @@ -4,7 +4,7 @@ auth: refresh_token: true secret_required: false access_token_expiration: 36000 -type: smart-on-fhir +type: smart-on-fhir-practitioner smart: name: Clinical guidelines launch_uri: https://beda.caresofa.com/ From fd8179e122d04fd2b2e7b080574927ca7004d940 Mon Sep 17 00:00:00 2001 From: Ilya Beda Date: Mon, 9 Dec 2024 13:59:14 +1100 Subject: [PATCH 5/9] Add ADHA CHAP Form smart app --- .../a57d90e3-5f69-4b92-aa2e-2992180863c2.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c2.yaml diff --git a/resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c2.yaml b/resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c2.yaml new file mode 100644 index 00000000..2261a9f9 --- /dev/null +++ b/resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c2.yaml @@ -0,0 +1,15 @@ +auth: + authorization_code: + redirect_uri: https://portal-xrp.digitalhealth.gov.au/FormsPortal/authorisation + refresh_token: true + secret_required: false + access_token_expiration: 36000 +type: smart-on-fhir-practitioner +smart: + name: ADHA CHAP Form + launch_uri: https://portal-xrp.digitalhealth.gov.au/FormsPortal/launch + description: ADHA CHAP Form +grant_types: + - authorization_code +id: a57d90e3-5f69-4b92-aa2e-2992180863c2 +resourceType: Client From 96b7d10aaa170270cb86e8a11bb0479140accae9 Mon Sep 17 00:00:00 2001 From: qscgyjqscgyj Date: Mon, 9 Dec 2024 18:35:32 +0100 Subject: [PATCH 6/9] Use sdc getChecker to check enableWhen values --- src/utils/__tests__/enableWhen/equal.test.ts | 2 +- src/utils/enableWhen.ts | 71 +++----------------- 2 files changed, 12 insertions(+), 61 deletions(-) diff --git a/src/utils/__tests__/enableWhen/equal.test.ts b/src/utils/__tests__/enableWhen/equal.test.ts index 84b9e118..83178d96 100644 --- a/src/utils/__tests__/enableWhen/equal.test.ts +++ b/src/utils/__tests__/enableWhen/equal.test.ts @@ -136,7 +136,7 @@ const ENABLE_WHEN_EQUAL_QUESTIONAIRES: QuestionnaireData[] = [ }, { linkId: 'q2', - answer: [{ value: { Coding: { code: 'test2', display: 'test2' } } }], + answer: [{ value: { Coding: { code: 'test2', display: 'Different display' } } }], }, { linkId: CONTROL_ITEM_LINK_ID, diff --git a/src/utils/enableWhen.ts b/src/utils/enableWhen.ts index 17fe0149..4d37872e 100644 --- a/src/utils/enableWhen.ts +++ b/src/utils/enableWhen.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import { getChecker } from 'sdc-qrf'; import type { QuestionnaireItemEnableWhenAnswer, QuestionnaireItemAnswerOption, @@ -6,51 +6,14 @@ import type { } from 'shared/src/contrib/aidbox'; import * as yup from 'yup'; -type EnableWhenAnswerTypes = 'Coding' | 'string' | 'integer' | 'boolean'; -export type EnableWhenOperator = 'exists' | '=' | '!=' | '>' | '<' | '>=' | '<='; -export type EnableWhenValueType = string | number | boolean; -type EnableWhenAnswerTypesMap = Record; - -const VALUES_TYPES: (keyof EnableWhenAnswerTypesMap)[] = ['Coding', 'string', 'integer', 'boolean']; -const VALUES_TYPES_PATH_MAP: EnableWhenAnswerTypesMap = { - Coding: { path: 'Coding' }, - string: { path: 'string' }, - integer: { path: 'integer' }, - boolean: { path: 'boolean' }, -}; - -const ENABLE_WHEN_OPERATORS_MAP: Record< - EnableWhenOperator, - (a: EnableWhenValueType, b: EnableWhenValueType) => boolean -> = { - exists: (a, b) => !!a === !!b, - '=': (a, b) => _.isEqual(a, b), - '!=': (a, b) => !_.isEqual(a, b), - '>': (a, b) => b > a, - '<': (a, b) => b < a, - '>=': (a, b) => b >= a, - '<=': (a, b) => b <= a, -}; - -function isOperatorValid(operator: string): operator is EnableWhenOperator { - return Object.keys(ENABLE_WHEN_OPERATORS_MAP).includes(operator); -} - -function getValueBYType(value: (QuestionnaireItemAnswerOption | undefined) | QuestionnaireItemEnableWhenAnswer) { - if (!value) { - return null; - } - - for (const valueTypePathKey of VALUES_TYPES) { - const path = VALUES_TYPES_PATH_MAP[valueTypePathKey].path; - - const valueResult = _.get(value, path) as EnableWhenAnswerTypes; - if (valueResult) { - return valueResult; +function getAnswerOptionsValues(answerOptionArray: QuestionnaireItemAnswerOption[]): Array<{ value: any }> { + return answerOptionArray.reduce>((acc, option) => { + if (option.value === undefined) { + return acc; } - } - return null; + return [...acc, { value: option.value }]; + }, []); } interface IsEnableWhenItemSucceedProps { @@ -61,29 +24,17 @@ interface IsEnableWhenItemSucceedProps { function isEnableWhenItemSucceed(props: IsEnableWhenItemSucceedProps): boolean { const { answerOptionArray, answer, operator } = props; - if (!isOperatorValid(operator)) { - return false; - } - if (!answerOptionArray || answerOptionArray.length === 0 || !answer) { return false; } - const value = getValueBYType(answer); - - if (!value) { + const answerOptionsWithValues = getAnswerOptionsValues(answerOptionArray); + if (answerOptionsWithValues.length === 0) { return false; } - return answerOptionArray.some((answerOption) => { - const answerOptionValue = getValueBYType(answerOption.value); - - if (answerOptionValue) { - return ENABLE_WHEN_OPERATORS_MAP[operator](value, answerOptionValue); - } - - return false; - }); + const checker = getChecker(operator); + return checker(answerOptionsWithValues, answer); } interface GetEnableWhenItemSchemaProps extends GetQuestionItemEnableWhenSchemaProps { From e4a6aa5d9f8310cffdc347d0b81362e7f4cd2854 Mon Sep 17 00:00:00 2001 From: Alex Lipovka Date: Tue, 10 Dec 2024 11:58:25 +0400 Subject: [PATCH 7/9] Add check for hidden items and refactor DocumentPrint --- .../PatientDetails/DocumentPrint/utils.ts | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/containers/PatientDetails/DocumentPrint/utils.ts b/src/containers/PatientDetails/DocumentPrint/utils.ts index 573f1153..71401eb1 100644 --- a/src/containers/PatientDetails/DocumentPrint/utils.ts +++ b/src/containers/PatientDetails/DocumentPrint/utils.ts @@ -1,41 +1,46 @@ import { QuestionnaireItem, QuestionnaireResponse } from 'fhir/r4b'; -import { evaluate } from 'src/utils'; +import { compileAsFirst } from 'src/utils'; -export function findQRItemValue(linkId: string, type = 'String') { - return `repeat(item).where(linkId='${linkId}').answer.value${type}`; -} +const qItemIsHidden = compileAsFirst( + "extension.where(url='http://hl7.org/fhir/StructureDefinition/questionnaire-hidden').exists() and extension.where(url='http://hl7.org/fhir/StructureDefinition/questionnaire-hidden').valueBoolean=true", +); + +const getQrItemValueByLinkIdAndType = (linkId: string, type: string) => + compileAsFirst(`repeat(item).where(linkId='${linkId}').answer.value${type}`); + +const questionnaireItemValueTypeMap: Record = { + display: 'String', + group: 'String', + text: 'String', + string: 'String', + decimal: 'Decimal', + integer: 'Integer', + date: 'Date', + dateTime: 'DateTime', + time: 'Time', + choice: 'Coding.display', + boolean: 'Boolean', + reference: 'Reference.display', + 'open-choice': '', + attachment: '', + quantity: '', + question: '', + url: '', +}; export function getQuestionnaireItemValue( questionnaireItem: QuestionnaireItem, questionnaireResponse: QuestionnaireResponse, ) { - switch (questionnaireItem.type) { - case 'display': - case 'group': - return ''; - case 'text': - case 'string': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'String'))[0]; - case 'decimal': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'Decimal'))[0]; - case 'integer': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'Integer'))[0]; - case 'date': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'Date'))[0]; - case 'dateTime': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'DateTime'))[0]; - case 'time': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'Time'))[0]; - case 'choice': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'Coding.display'))[0]; - case 'boolean': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'Boolean'))[0]; - case 'reference': - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId, 'Reference.display'))[0]; - default: - return evaluate(questionnaireResponse, findQRItemValue(questionnaireItem.linkId))[0]; + if (qItemIsHidden(questionnaireItem)) { + return undefined; } + + return getQrItemValueByLinkIdAndType( + questionnaireItem.linkId, + questionnaireItemValueTypeMap[questionnaireItem.type], + )(questionnaireResponse); } export function flattenQuestionnaireGroupItems(item: QuestionnaireItem): QuestionnaireItem[] { From bfd075c13b8c0005aad1b89c82c5f7d63aa0d5d4 Mon Sep 17 00:00:00 2001 From: Elena Tretyakova Date: Tue, 10 Dec 2024 16:36:05 +0100 Subject: [PATCH 8/9] Update ChangesDiff component (#405) * Update ChangesDiff component * Fix ChangesDiff stories --- .../ChangesDiff/ChangesDiff.stories.tsx | 6 +-- src/components/ChangesDiff/index.tsx | 37 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/components/ChangesDiff/ChangesDiff.stories.tsx b/src/components/ChangesDiff/ChangesDiff.stories.tsx index 8d7a8267..510c1ac2 100644 --- a/src/components/ChangesDiff/ChangesDiff.stories.tsx +++ b/src/components/ChangesDiff/ChangesDiff.stories.tsx @@ -2,7 +2,7 @@ import { Meta, StoryObj } from '@storybook/react'; import { WithQuestionFormProviderDecorator, withColorSchemeDecorator } from 'src/storybook/decorators'; -import { ChangesDiff, Props } from './index'; +import { ChangesDiff, ChangesDiffProps } from './index'; const meta: Meta = { title: 'components / ChangesDiff', @@ -21,7 +21,7 @@ export const Deletions: Story = { render: () => , }; -const props1: Props = { +const props1: ChangesDiffProps = { id: '6d2d6fe6-beba-4ce6-9a9f-dd0d4b06d4e5', activityCode: 'CREATE', recorded: '2023-07-19T14:16:19.825125Z', @@ -48,7 +48,7 @@ const props1: Props = { ], }; -const props2: Props = { +const props2: ChangesDiffProps = { id: '27613e5f-e8dd-4a9c-8d36-dfd2f1089d5e', activityCode: 'UPDATE', recorded: '2023-07-19T14:16:19.825125Z', diff --git a/src/components/ChangesDiff/index.tsx b/src/components/ChangesDiff/index.tsx index e3e16af1..9ead712d 100644 --- a/src/components/ChangesDiff/index.tsx +++ b/src/components/ChangesDiff/index.tsx @@ -5,39 +5,44 @@ import { Text } from 'src/components/Typography'; import { formatHumanDateTime } from 'src/utils/date'; import { S } from './ChangesDiff.styles'; +import { CSSProperties } from 'react'; -interface Change { +export interface ChangesDiffChange { key: string; title: string; valueBefore: string | null; valueAfter: string | null; } -export interface Props { +export interface ChangesDiffProps { id: string; - changes: Change[]; - activityCode: string; - recorded: string; - author: string[]; + changes: ChangesDiffChange[]; + activityCode?: string; + recorded?: string; + author?: string[]; + className?: string | undefined; + style?: CSSProperties | undefined; } -export function ChangesDiff(props: Props) { - const { changes, id, activityCode, recorded, author = [] } = props; +export function ChangesDiff(props: ChangesDiffProps) { + const { changes, id, activityCode, recorded, author = [], className, style } = props; const codesMapping = { CREATE: t`Created`, UPDATE: t`Updated`, }; - const activity = codesMapping[activityCode]; - const date = formatHumanDateTime(recorded); + const activity = activityCode ? codesMapping[activityCode] : null; + const date = recorded ? formatHumanDateTime(recorded) : null; const by = author.join(', '); return ( - - - - {activity} {date} by {by} - - + + {activity ? ( + + + {activity} {date} by {by} + + + ) : null} {changes.map((item) => (
{item.title} From 8545c1bc1918941a69ab0f14a1f677c71e66cee0 Mon Sep 17 00:00:00 2001 From: Elena Tretyakova Date: Tue, 10 Dec 2024 17:10:41 +0100 Subject: [PATCH 9/9] Add audio recorder item control component (#403) * Add audio recorder item control component * Update file uploader stories * Extract locales --- src/components/AudioRecorder/hooks.ts | 19 +++ src/components/AudioRecorder/index.tsx | 73 ++++++++++ src/components/AudioRecorder/styles.ts | 50 +++++++ .../ReadonlyQuestionnaireResponseForm.tsx | 6 +- .../controls.tsx | 2 + .../readonly-widgets/AudioAttachment.tsx | 34 +++++ .../ReadonlyWidgets.styles.ts | 4 + .../readonly-widgets/UploadFile.tsx | 28 ++++ .../widgets/AudioRecorderUploader/index.tsx | 99 ++++++++++++++ .../widgets/AudioRecorderUploader/styles.ts | 13 ++ .../UploadFileControl.stories.tsx | 26 ++-- .../widgets/UploadFileControl/hooks.ts | 13 +- .../widgets/UploadFileControl/index.tsx | 15 +-- src/locale/en/messages.po | 125 +++++++++++------- src/locale/es/messages.po | 125 +++++++++++------- src/locale/ru/messages.po | 125 +++++++++++------- 16 files changed, 579 insertions(+), 178 deletions(-) create mode 100644 src/components/AudioRecorder/hooks.ts create mode 100644 src/components/AudioRecorder/index.tsx create mode 100644 src/components/AudioRecorder/styles.ts create mode 100644 src/components/BaseQuestionnaireResponseForm/readonly-widgets/AudioAttachment.tsx create mode 100644 src/components/BaseQuestionnaireResponseForm/readonly-widgets/UploadFile.tsx create mode 100644 src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/index.tsx create mode 100644 src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/styles.ts diff --git a/src/components/AudioRecorder/hooks.ts b/src/components/AudioRecorder/hooks.ts new file mode 100644 index 00000000..90db8b93 --- /dev/null +++ b/src/components/AudioRecorder/hooks.ts @@ -0,0 +1,19 @@ +// eslint-disable-next-line +import { useAudioRecorder as useAudioRecorderControl } from "react-audio-voice-recorder"; + +export interface RecorderControls { + startRecording: () => void; + stopRecording: () => void; + togglePauseResume: () => void; + recordingBlob?: Blob; + isRecording: boolean; + isPaused: boolean; + recordingTime: number; + mediaRecorder?: MediaRecorder; +} + +export function useAudioRecorder() { + const recorderControls: RecorderControls = useAudioRecorderControl(); + + return { recorderControls }; +} \ No newline at end of file diff --git a/src/components/AudioRecorder/index.tsx b/src/components/AudioRecorder/index.tsx new file mode 100644 index 00000000..6d300bb2 --- /dev/null +++ b/src/components/AudioRecorder/index.tsx @@ -0,0 +1,73 @@ +import { Trans } from '@lingui/macro'; +// eslint-disable-next-line +import { AudioRecorder as AudioRecorderControl } from 'react-audio-voice-recorder'; + +import { RecorderControls } from './hooks'; +import { S } from './styles'; +import { uuid4 } from '@beda.software/fhir-react'; +import { Upload, type UploadFile } from 'antd'; +import React from 'react'; +import { RcFile } from 'antd/lib/upload/interface'; + +interface AudioRecorderProps { + onChange: (url: RcFile) => Promise; + recorderControls: RecorderControls; +} + +export function AudioRecorder(props: AudioRecorderProps) { + const { recorderControls, onChange } = props; + + const onRecordingComplete = async (blob: Blob) => { + const uuid = uuid4(); + const audioFile = new File([blob], `${uuid}.webm`, { type: blob.type }) as RcFile; + audioFile.uid = uuid; + onChange(audioFile); + }; + + return ( + + + Capture in progress + + + + ); +} + +interface AudioPlayerProps { + files: UploadFile[]; + onRemove?: (file: UploadFile) => void; +} + +export function AudioPlayer(props: AudioPlayerProps) { + const { files, onRemove } = props; + + return ( + + + Listen to the audio + + {files.map((file) => ( + + + onRemove?.(file)} + /> + + ))} + + ); +} diff --git a/src/components/AudioRecorder/styles.ts b/src/components/AudioRecorder/styles.ts new file mode 100644 index 00000000..6e44e420 --- /dev/null +++ b/src/components/AudioRecorder/styles.ts @@ -0,0 +1,50 @@ +import styled, { css } from 'styled-components'; + +import { Text } from 'src/components/Typography'; + +export const S = { + Scriber: styled.div` + display: flex; + flex-direction: column; + gap: 8px 0; + + .audio-recorder { + width: 100%; + box-shadow: none; + border-radius: 30px; + background-color: ${({ theme }) => theme.neutralPalette.gray_2}; + padding: 3px 6px 3px 18px; + } + + .audio-recorder-timer, + .audio-recorder-status { + font-family: inherit; + color: ${({ theme }) => theme.neutralPalette.gray_12}; + } + + .audio-recorder-mic { + display: none; + } + + .audio-recorder-timer { + margin-left: 0; + } + + .audio-recorder-options { + filter: ${({ theme }) => (theme.mode === 'dark' ? `invert(100%)` : `invert(0%)`)}; + } + `, + Title: styled(Text)<{ $danger?: boolean }>` + font-weight: 700; + + ${({ $danger }) => + $danger && + css` + color: ${({ theme }) => theme.antdTheme?.red5}; + `} + `, + Audio: styled.audio` + height: 52px; + width: 100%; + `, +}; diff --git a/src/components/BaseQuestionnaireResponseForm/ReadonlyQuestionnaireResponseForm.tsx b/src/components/BaseQuestionnaireResponseForm/ReadonlyQuestionnaireResponseForm.tsx index e266d122..a0140bcc 100644 --- a/src/components/BaseQuestionnaireResponseForm/ReadonlyQuestionnaireResponseForm.tsx +++ b/src/components/BaseQuestionnaireResponseForm/ReadonlyQuestionnaireResponseForm.tsx @@ -18,7 +18,8 @@ import { QuestionReference } from './readonly-widgets/reference'; import { AnxietyScore, DepressionScore } from './readonly-widgets/score'; import { QuestionText, TextWithInput } from './readonly-widgets/string'; import { TimeRangePickerControl } from './readonly-widgets/TimeRangePickerControl'; -import { UploadFileControlReadOnly } from './widgets/UploadFileControl'; +import { UploadFile } from './readonly-widgets/UploadFile'; +import { AudioAttachment } from './readonly-widgets/AudioAttachment'; interface Props extends Partial { formData: QuestionnaireResponseFormData; @@ -66,7 +67,7 @@ export function ReadonlyQuestionnaireResponseForm(props: Props) { reference: QuestionReference, display: Display, boolean: QuestionBoolean, - attachment: UploadFileControlReadOnly, + attachment: UploadFile, ...questionItemComponents, }} itemControlQuestionItemComponents={{ @@ -74,6 +75,7 @@ export function ReadonlyQuestionnaireResponseForm(props: Props) { 'anxiety-score': AnxietyScore, 'depression-score': DepressionScore, 'input-inside-text': TextWithInput, + 'audio-recorder-uploader': AudioAttachment, ...itemControlQuestionItemComponents, }} > diff --git a/src/components/BaseQuestionnaireResponseForm/controls.tsx b/src/components/BaseQuestionnaireResponseForm/controls.tsx index d3212f54..9e0e754c 100644 --- a/src/components/BaseQuestionnaireResponseForm/controls.tsx +++ b/src/components/BaseQuestionnaireResponseForm/controls.tsx @@ -35,6 +35,7 @@ import { QuestionReference } from './widgets/reference'; import { ReferenceRadioButton } from './widgets/ReferenceRadioButton'; import { UploadFileControl } from './widgets/UploadFileControl'; import { TextWithMacroFill } from '../TextWithMacroFill'; +import { AudioRecorderUploader } from './widgets/AudioRecorderUploader'; export const itemComponents: QuestionItemComponentMapping = { text: QuestionText, @@ -66,6 +67,7 @@ export const itemControlComponents: ItemControlQuestionItemComponentMapping = { 'check-box': InlineChoice, 'input-inside-text': QuestionInputInsideText, 'markdown-editor': MDEditorControl, + 'audio-recorder-uploader': AudioRecorderUploader, }; export const groupControlComponents: ItemControlGroupItemComponentMapping = { diff --git a/src/components/BaseQuestionnaireResponseForm/readonly-widgets/AudioAttachment.tsx b/src/components/BaseQuestionnaireResponseForm/readonly-widgets/AudioAttachment.tsx new file mode 100644 index 00000000..55b0d61e --- /dev/null +++ b/src/components/BaseQuestionnaireResponseForm/readonly-widgets/AudioAttachment.tsx @@ -0,0 +1,34 @@ +import { Upload } from 'antd'; +import { QuestionItemProps } from 'sdc-qrf'; +import classNames from 'classnames'; + +import s from './ReadonlyWidgets.module.scss'; +import { S } from './ReadonlyWidgets.styles'; +import { useUploader } from '../widgets/UploadFileControl/hooks'; +import React from 'react'; + +export function AudioAttachment(props: QuestionItemProps) { + const { questionItem } = props; + const { text, hidden } = questionItem; + const { fileList } = useUploader(props); + + if (hidden) { + return null; + } + + return ( + + {text} + {fileList.length ? ( + fileList.map((file) => ( + + + + + )) + ) : ( + - + )} + + ); +} diff --git a/src/components/BaseQuestionnaireResponseForm/readonly-widgets/ReadonlyWidgets.styles.ts b/src/components/BaseQuestionnaireResponseForm/readonly-widgets/ReadonlyWidgets.styles.ts index a1d76e3a..965eae6c 100644 --- a/src/components/BaseQuestionnaireResponseForm/readonly-widgets/ReadonlyWidgets.styles.ts +++ b/src/components/BaseQuestionnaireResponseForm/readonly-widgets/ReadonlyWidgets.styles.ts @@ -16,4 +16,8 @@ export const S = { border-top: 1px solid ${({ theme }) => theme.neutralPalette.gray_4}; } `, + Audio: styled.audio` + height: 52px; + width: 100%; + `, }; diff --git a/src/components/BaseQuestionnaireResponseForm/readonly-widgets/UploadFile.tsx b/src/components/BaseQuestionnaireResponseForm/readonly-widgets/UploadFile.tsx new file mode 100644 index 00000000..0c097a7a --- /dev/null +++ b/src/components/BaseQuestionnaireResponseForm/readonly-widgets/UploadFile.tsx @@ -0,0 +1,28 @@ +import { Upload } from 'antd'; +import { QuestionItemProps } from 'sdc-qrf'; +import classNames from 'classnames'; + +import s from './ReadonlyWidgets.module.scss'; +import { S } from './ReadonlyWidgets.styles'; +import { useUploader } from '../widgets/UploadFileControl/hooks'; + +export function UploadFile(props: QuestionItemProps) { + const { questionItem } = props; + const { text, hidden } = questionItem; + const { fileList } = useUploader(props); + + if (hidden) { + return null; + } + + return ( + + {text} + {fileList.length ? ( + + ) : ( + - + )} + + ); +} diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/index.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/index.tsx new file mode 100644 index 00000000..ac92ee61 --- /dev/null +++ b/src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/index.tsx @@ -0,0 +1,99 @@ +import { AudioOutlined } from '@ant-design/icons'; +import { Trans } from '@lingui/macro'; +import { Form, UploadFile } from 'antd'; +import { useCallback, useState } from 'react'; +import { QuestionItemProps } from 'sdc-qrf'; + +import { AudioPlayer as AudioPlayerControl, AudioRecorder as AudioRecorderControl } from 'src/components/AudioRecorder'; +import { useAudioRecorder } from 'src/components/AudioRecorder/hooks'; + +import { useUploader } from '../UploadFileControl/hooks'; +import { RcFile } from 'antd/lib/upload/interface'; +import { isSuccess } from '@beda.software/remote-data'; +import { S } from './styles'; +import { UploadFileControl } from '../UploadFileControl'; + +export function AudioRecorderUploader(props: QuestionItemProps) { + const { questionItem } = props; + const [showScriber, setShowScriber] = useState(false); + + const { recorderControls } = useAudioRecorder(); + const { formItem, customRequest, onChange, fileList } = useUploader(props); + const hasFiles = fileList.length > 0; + + const onScribeChange = useCallback( + async (file: RcFile) => { + setShowScriber(false); + + const fileClone = new File([file], file.name, { + type: file.type, + }) as any as UploadFile; + fileClone.uid = file.uid; + fileClone.status = 'uploading'; + fileClone.percent = 0; + + onChange({ + fileList: [...fileList, fileClone], + file: fileClone, + }); + + const response = await customRequest({ file }); + + if (isSuccess(response)) { + fileClone.status = 'done'; + fileClone.url = response.data.uploadUrl; + fileClone.percent = 100; + + onChange({ + fileList: [...fileList, fileClone], + file: fileClone, + }); + } + }, + [fileList], + ); + + const renderContent = () => { + if (hasFiles) { + return ( + + + + ); + } + + if (showScriber) { + return ( + + + + ); + } + + return ( + <> + } + type="primary" + onClick={() => { + setShowScriber(true); + recorderControls.startRecording(); + }} + > + + Start scribe + + + + + ); + }; + + return {renderContent()}; +} diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/styles.ts b/src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/styles.ts new file mode 100644 index 00000000..3224ffee --- /dev/null +++ b/src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/styles.ts @@ -0,0 +1,13 @@ +import { Button } from 'antd'; +import styled from 'styled-components'; + +export const S = { + Container: styled.div` + border-radius: 10px; + padding: 12px 8px; + border: 1px solid ${({ theme }) => theme.neutralPalette.gray_4}; + `, + Button: styled(Button)` + width: 100%; + `, +}; diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/UploadFileControl.stories.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/UploadFileControl.stories.tsx index 39de58e3..6aef356d 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/UploadFileControl.stories.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/UploadFileControl.stories.tsx @@ -4,6 +4,8 @@ import { ItemContext } from 'sdc-qrf/lib/types'; import { WithQuestionFormProviderDecorator, withColorSchemeDecorator } from 'src/storybook/decorators'; import { UploadFileControl } from './index'; +import { I18nProvider } from '@lingui/react'; +import { i18n } from '@lingui/core'; const meta: Meta = { title: 'Questionnaire / questions / UploadFileControl', @@ -14,17 +16,21 @@ const meta: Meta = { export default meta; type Story = StoryObj; +i18n.activate('en'); + export const Default: Story = { render: () => ( - + + + ), }; diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/hooks.ts b/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/hooks.ts index fba13ebb..2713a756 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/hooks.ts +++ b/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/hooks.ts @@ -1,7 +1,7 @@ import type { UploadFile } from 'antd'; import { notification } from 'antd'; import { Attachment } from 'fhir/r4b'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { QuestionItemProps } from 'sdc-qrf'; import { formatError } from '@beda.software/fhir-react'; @@ -22,8 +22,9 @@ export function useUploader({ parentPath, questionItem }: QuestionItemProps) { const { linkId, repeats } = questionItem; const fieldName = [...parentPath, linkId]; const { formItem, value, onChange } = useFieldController(fieldName, questionItem); + const uid = useRef>({}); - const initialFileList: Array = (value ?? []).map((v: ValueAttachment) => { + const initialFileList: Array = useMemo(() => (value ?? []).map((v: ValueAttachment) => { const url = v.value.Attachment.url!; const file: UploadFile = { uid: url, @@ -31,9 +32,13 @@ export function useUploader({ parentPath, questionItem }: QuestionItemProps) { percent: 100, }; return file; - }); + }), [value]); const [fileList, setFileList] = useState>(initialFileList); + useEffect(() => { + setFileList(initialFileList); + }, [JSON.stringify(initialFileList)]); + useEffect(() => { (async () => { const result: Array = []; @@ -65,6 +70,7 @@ export function useUploader({ parentPath, questionItem }: QuestionItemProps) { async (options: CustomUploadRequestOption) => { const file: UploadFile = options.file as any; const response = await generateUploadUrl(file.name); + if (isSuccess(response)) { const { filename, uploadUrl } = response.data; uid.current[file.uid] = filename; @@ -72,6 +78,7 @@ export function useUploader({ parentPath, questionItem }: QuestionItemProps) { } else { notification.error({ message: formatError(response.error) }); } + return response; }, [uid], ); diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/index.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/index.tsx index b508ed42..1cf0ec57 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/index.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/index.tsx @@ -3,12 +3,14 @@ import { Form, Upload } from 'antd'; import { QuestionItemProps } from 'sdc-qrf'; import { useUploader } from './hooks'; +import { Trans } from '@lingui/macro'; const { Dragger } = Upload; export function UploadFileControl(props: QuestionItemProps) { const { showDragger, formItem, customRequest, onChange, onRemove, fileList } = useUploader(props); const { helpText, repeats } = props.questionItem; + return ( {showDragger ? ( @@ -23,7 +25,9 @@ export function UploadFileControl(props: QuestionItemProps) {

-

Click or drag file to this area to upload

+

+ Click or drag file to this area to upload +

{helpText}

) : ( @@ -37,12 +41,3 @@ export function UploadFileControl(props: QuestionItemProps) {
); } - -export function UploadFileControlReadOnly(props: QuestionItemProps) { - const { formItem, fileList } = useUploader(props); - return ( - - - - ); -} diff --git a/src/locale/en/messages.po b/src/locale/en/messages.po index e9451aaa..2c3a7cc1 100644 --- a/src/locale/en/messages.po +++ b/src/locale/en/messages.po @@ -48,12 +48,12 @@ msgstr "" msgid "Active" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:223 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:232 #: src/containers/PatientDetails/PatientResources/utils.tsx:24 msgid "Active Medications" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:157 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:163 msgid "Activities" msgstr "" @@ -128,7 +128,7 @@ msgstr "" msgid "Aidbox Forms Builder" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:34 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:35 #: src/containers/PatientDetails/PatientResources/utils.tsx:98 msgid "Allergies" msgstr "" @@ -225,7 +225,7 @@ msgstr "" msgid "Calories" msgstr "" -#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:43 +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:117 #: src/containers/DocumentsList/ChooseDocumentToCreateModal/index.tsx:54 #: src/containers/InvoiceList/components/ModalCancelInvoice/index.tsx:18 #: src/containers/Prescriptions/ModalMedicationRequestCancel.tsx:22 @@ -247,6 +247,7 @@ msgstr "" msgid "Cancelled" msgstr "" +#: src/components/AudioRecorder/index.tsx:30 #: src/containers/EncounterDetails/AIScribe/index.tsx:93 msgid "Capture in progress" msgstr "" @@ -255,6 +256,10 @@ msgstr "" msgid "Characteristics" msgstr "" +#: src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/index.tsx:29 +msgid "Click or drag file to this area to upload" +msgstr "" + #: src/containers/PractitionerDetails/PractitionerOverview/index.tsx:92 msgid "Clinician successfully updated" msgstr "" @@ -295,7 +300,7 @@ msgstr "" msgid "Concepts:" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:72 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:75 #: src/containers/PatientDetails/PatientResources/utils.tsx:59 msgid "Conditions" msgstr "" @@ -308,7 +313,7 @@ msgstr "" msgid "Confirm Medication Request" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:110 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:115 #: src/containers/PatientDetails/PatientResources/utils.tsx:172 msgid "Consents" msgstr "" @@ -337,9 +342,9 @@ msgstr "" msgid "Create document" msgstr "" -#: src/components/ModalNewEncounter/index.tsx:22 -#: src/components/ModalNewEncounter/index.tsx:24 +#: src/components/ModalNewEncounter/index.tsx:23 #: src/components/ModalNewEncounter/index.tsx:25 +#: src/components/ModalNewEncounter/index.tsx:26 msgid "Create Encounter" msgstr "" @@ -368,11 +373,11 @@ msgstr "" #: src/containers/EncounterList/index.tsx:64 #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:26 #: src/containers/InvoiceList/tableUtils.tsx:129 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:53 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:91 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:136 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:208 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:273 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:55 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:95 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:142 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:216 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:283 #: src/containers/PatientDetails/PatientResources/utils.tsx:85 #: src/containers/PatientDetails/PatientResources/utils.tsx:124 #: src/containers/PatientDetails/PatientResources/utils.tsx:163 @@ -452,7 +457,7 @@ msgstr "" msgid "Documents have been successfully extracted" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:242 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:252 #: src/containers/PatientDetails/PatientResources/utils.tsx:50 msgid "Dosage" msgstr "" @@ -527,7 +532,7 @@ msgstr "" msgid "Encounter completed" msgstr "" -#: src/components/ModalNewEncounter/index.tsx:38 +#: src/components/ModalNewEncounter/index.tsx:39 msgid "Encounter successfully created" msgstr "" @@ -535,8 +540,8 @@ msgstr "" msgid "Encounter was successfully completed" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:48 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:54 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:28 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:34 #: src/components/PatientEncounter/index.tsx:79 #: src/containers/EncounterList/index.tsx:103 #: src/containers/PatientDetails/PatientHeader/index.tsx:79 @@ -672,7 +677,7 @@ msgstr "" msgid "IL-6" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:189 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:196 #: src/containers/PatientDetails/PatientResources/utils.tsx:137 msgid "Immunization" msgstr "" @@ -689,7 +694,7 @@ msgstr "" msgid "Inline options?" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:307 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:317 msgid "Intent" msgstr "" @@ -701,9 +706,9 @@ msgstr "" msgid "Invoice was successfully payed" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:46 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:58 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:61 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:26 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:40 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:44 #: src/containers/InvoiceList/index.tsx:44 msgid "Invoices" msgstr "" @@ -728,6 +733,10 @@ msgstr "" msgid "Last name" msgstr "" +#: src/components/AudioRecorder/index.tsx:58 +msgid "Listen to the audio" +msgstr "" + #: src/containers/SignIn/index.tsx:89 msgid "Log in" msgstr "" @@ -740,7 +749,7 @@ msgstr "" #~ msgid "Log in as Practitioner" #~ msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarBottom/index.tsx:96 +#: src/components/BaseLayout/Sidebar/SidebarBottom/context.tsx:42 msgid "Log out" msgstr "" @@ -777,7 +786,7 @@ msgstr "" msgid "Medication request successfully confirmed" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:62 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:45 #: src/containers/MedicationManagement/index.tsx:28 msgid "Medications" msgstr "" @@ -795,12 +804,12 @@ msgid "Monday" msgstr "" #: src/containers/MedicationManagement/index.tsx:54 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:42 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:80 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:118 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:197 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:231 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:302 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:43 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:83 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:123 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:204 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:240 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:312 #: src/containers/PatientDetails/PatientResources/utils.tsx:39 #: src/containers/PatientDetails/PatientResources/utils.tsx:74 #: src/containers/PatientDetails/PatientResources/utils.tsx:113 @@ -824,7 +833,7 @@ msgstr "" #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:151 #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:176 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:137 -#: src/utils/questionnaire.ts:49 +#: src/utils/questionnaire.ts:63 msgid "No" msgstr "" @@ -908,7 +917,7 @@ msgid "Order added" msgstr "" #: src/containers/PatientDetails/PatientHeader/index.tsx:82 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:294 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:304 msgid "Orders" msgstr "" @@ -928,6 +937,7 @@ msgstr "" msgid "Password" msgstr "" +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:39 #: src/containers/EncounterList/index.tsx:46 #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:20 #: src/containers/InvoiceList/components/InvoiceListSearchBar/index.tsx:39 @@ -951,8 +961,8 @@ msgstr "" msgid "Patient successfully created" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:49 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:55 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:29 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:35 #: src/containers/PatientDetails/PatientHeader/index.tsx:26 #: src/containers/PatientList/index.tsx:59 msgid "Patients" @@ -980,8 +990,8 @@ msgstr "" #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:23 #: src/containers/InvoiceList/components/InvoiceListSearchBar/index.tsx:33 #: src/containers/InvoiceList/tableUtils.tsx:114 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:146 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:265 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:152 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:275 #: src/containers/PatientDetails/PatientResources/utils.tsx:210 msgid "Practitioner" msgstr "" @@ -990,7 +1000,7 @@ msgstr "" msgid "Practitioner successfully created" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:50 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:30 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:37 #: src/containers/PractitionerList/index.tsx:77 msgid "Practitioners" @@ -1000,7 +1010,7 @@ msgstr "" msgid "Prepare for print" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:63 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:46 #: src/containers/Prescriptions/index.tsx:48 msgid "Prescriptions" msgstr "" @@ -1026,8 +1036,8 @@ msgstr "" msgid "Questionnaire properties" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:51 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:56 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:31 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:36 #: src/containers/QuestionnaireList/index.tsx:80 msgid "Questionnaires" msgstr "" @@ -1044,7 +1054,7 @@ msgstr "" msgid "Repeats" msgstr "" -#: src/components/ModalNewEncounter/index.tsx:23 +#: src/components/ModalNewEncounter/index.tsx:24 msgid "Request Appointment" msgstr "" @@ -1081,7 +1091,7 @@ msgstr "" msgid "Saturday" msgstr "" -#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:53 +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:127 #: src/containers/EncounterDetails/AIScribe/index.tsx:182 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:130 #: src/containers/QuestionnaireBuilder/QuestionnaireSaveForm/index.tsx:90 @@ -1090,16 +1100,28 @@ msgstr "" msgid "Save" msgstr "" +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:77 +msgid "Save as draft" +msgstr "" + #: src/containers/QuestionnaireBuilder/index.tsx:57 #: src/containers/QuestionnaireBuilder/index.tsx:58 msgid "Save questionnaire" msgstr "" +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:94 +msgid "Saved as draft" +msgstr "" + +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:85 +msgid "Saving draft" +msgstr "" + #: src/containers/Scheduling/ScheduleCalendar/index.tsx:41 msgid "Schedule calendar" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:60 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:43 #: src/containers/OrganizationScheduling/index.tsx:71 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:39 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:71 @@ -1143,8 +1165,8 @@ msgstr "" msgid "Select (default)" msgstr "" -#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:25 -#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:50 +#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:30 +#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:55 msgid "Select..." msgstr "" @@ -1152,7 +1174,7 @@ msgstr "" msgid "Serum creatinin" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:269 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:279 msgid "Service" msgstr "" @@ -1160,7 +1182,7 @@ msgstr "" msgid "Service Requests" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:47 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:27 msgid "Services" msgstr "" @@ -1205,6 +1227,7 @@ msgstr "" msgid "Start date" msgstr "" +#: src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/index.tsx:84 #: src/containers/EncounterDetails/index.tsx:104 msgid "Start scribe" msgstr "" @@ -1294,11 +1317,11 @@ msgstr "" msgid "Text with macro" msgstr "" -#: src/containers/App/index.tsx:125 +#: src/containers/App/index.tsx:129 msgid "Thank you for filling out the questionnaire. Now you can close this page." msgstr "" -#: src/containers/App/index.tsx:124 +#: src/containers/App/index.tsx:128 msgid "Thank you!" msgstr "" @@ -1331,7 +1354,7 @@ msgstr "" msgid "Thursday" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:277 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:287 #: src/containers/Scheduling/Availability/index.tsx:129 msgid "Time" msgstr "" @@ -1441,7 +1464,7 @@ msgstr "" #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:150 #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:175 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:136 -#: src/utils/questionnaire.ts:49 +#: src/utils/questionnaire.ts:63 msgid "Yes" msgstr "" diff --git a/src/locale/es/messages.po b/src/locale/es/messages.po index 99a39b1c..133aea96 100644 --- a/src/locale/es/messages.po +++ b/src/locale/es/messages.po @@ -44,12 +44,12 @@ msgstr "Activar servicio clínico" msgid "Active" msgstr "Activo" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:223 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:232 #: src/containers/PatientDetails/PatientResources/utils.tsx:24 msgid "Active Medications" msgstr "Medicamentos Activos" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:157 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:163 msgid "Activities" msgstr "Actividades" @@ -124,7 +124,7 @@ msgstr "Creador de IA" msgid "Aidbox Forms Builder" msgstr "Constructor de Formularios Aidbox" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:34 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:35 #: src/containers/PatientDetails/PatientResources/utils.tsx:98 msgid "Allergies" msgstr "Alergias" @@ -221,7 +221,7 @@ msgstr "Construye tu formulario" msgid "Calories" msgstr "Calorías" -#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:43 +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:117 #: src/containers/DocumentsList/ChooseDocumentToCreateModal/index.tsx:54 #: src/containers/InvoiceList/components/ModalCancelInvoice/index.tsx:18 #: src/containers/Prescriptions/ModalMedicationRequestCancel.tsx:22 @@ -243,6 +243,7 @@ msgstr "Cancelar solicitud de medicamento" msgid "Cancelled" msgstr "Cancelado" +#: src/components/AudioRecorder/index.tsx:30 #: src/containers/EncounterDetails/AIScribe/index.tsx:93 msgid "Capture in progress" msgstr "Ejecución en curso" @@ -251,6 +252,10 @@ msgstr "Ejecución en curso" msgid "Characteristics" msgstr "Características" +#: src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/index.tsx:29 +msgid "Click or drag file to this area to upload" +msgstr "" + #: src/containers/PractitionerDetails/PractitionerOverview/index.tsx:92 msgid "Clinician successfully updated" msgstr "Profesional actualizado exitosamente" @@ -291,7 +296,7 @@ msgstr "Completado" msgid "Concepts:" msgstr "Conceptos:" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:72 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:75 #: src/containers/PatientDetails/PatientResources/utils.tsx:59 msgid "Conditions" msgstr "Condiciones" @@ -304,7 +309,7 @@ msgstr "Confirmar" msgid "Confirm Medication Request" msgstr "Confirmar solicitud de medicamento" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:110 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:115 #: src/containers/PatientDetails/PatientResources/utils.tsx:172 msgid "Consents" msgstr "Consentimientos" @@ -333,9 +338,9 @@ msgstr "Crear" msgid "Create document" msgstr "Crear documento" -#: src/components/ModalNewEncounter/index.tsx:22 -#: src/components/ModalNewEncounter/index.tsx:24 +#: src/components/ModalNewEncounter/index.tsx:23 #: src/components/ModalNewEncounter/index.tsx:25 +#: src/components/ModalNewEncounter/index.tsx:26 msgid "Create Encounter" msgstr "Crear un encuentro" @@ -364,11 +369,11 @@ msgstr "" #: src/containers/EncounterList/index.tsx:64 #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:26 #: src/containers/InvoiceList/tableUtils.tsx:129 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:53 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:91 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:136 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:208 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:273 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:55 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:95 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:142 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:216 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:283 #: src/containers/PatientDetails/PatientResources/utils.tsx:85 #: src/containers/PatientDetails/PatientResources/utils.tsx:124 #: src/containers/PatientDetails/PatientResources/utils.tsx:163 @@ -444,7 +449,7 @@ msgstr "Documentos" msgid "Documents have been successfully extracted" msgstr "Los documentos han sido obtenidos exitosamente" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:242 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:252 #: src/containers/PatientDetails/PatientResources/utils.tsx:50 msgid "Dosage" msgstr "Dosis" @@ -519,7 +524,7 @@ msgstr "Encuentro" msgid "Encounter completed" msgstr "Encuentro completado" -#: src/components/ModalNewEncounter/index.tsx:38 +#: src/components/ModalNewEncounter/index.tsx:39 msgid "Encounter successfully created" msgstr "Encuentro creado exitosamente" @@ -527,8 +532,8 @@ msgstr "Encuentro creado exitosamente" msgid "Encounter was successfully completed" msgstr "El encuentro se completó con éxito" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:48 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:54 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:28 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:34 #: src/components/PatientEncounter/index.tsx:79 #: src/containers/EncounterList/index.tsx:103 #: src/containers/PatientDetails/PatientHeader/index.tsx:79 @@ -664,7 +669,7 @@ msgstr "Historial de cambios" msgid "IL-6" msgstr "IL-6" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:189 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:196 #: src/containers/PatientDetails/PatientResources/utils.tsx:137 msgid "Immunization" msgstr "Inmunización" @@ -681,7 +686,7 @@ msgstr "Elección en línea" msgid "Inline options?" msgstr "¿Opciones en línea?" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:307 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:317 msgid "Intent" msgstr "Intención" @@ -693,9 +698,9 @@ msgstr "La factura fue cancelada con éxito" msgid "Invoice was successfully payed" msgstr "La factura fue pagada con éxito" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:46 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:58 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:61 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:26 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:40 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:44 #: src/containers/InvoiceList/index.tsx:44 msgid "Invoices" msgstr "Facturas" @@ -720,6 +725,10 @@ msgstr "Etiqueta" msgid "Last name" msgstr "Apellido" +#: src/components/AudioRecorder/index.tsx:58 +msgid "Listen to the audio" +msgstr "" + #: src/containers/SignIn/index.tsx:89 msgid "Log in" msgstr "Iniciar sesión" @@ -728,7 +737,7 @@ msgstr "Iniciar sesión" msgid "Log in as demo patient" msgstr "Iniciar sesión como paciente demo" -#: src/components/BaseLayout/Sidebar/SidebarBottom/index.tsx:96 +#: src/components/BaseLayout/Sidebar/SidebarBottom/context.tsx:42 msgid "Log out" msgstr "Cerrar sesión" @@ -765,7 +774,7 @@ msgstr "Solicitud de medicamento cancelada con éxito" msgid "Medication request successfully confirmed" msgstr "Solicitud de medicamento confirmada con éxito" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:62 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:45 #: src/containers/MedicationManagement/index.tsx:28 msgid "Medications" msgstr "Medicaciones" @@ -783,12 +792,12 @@ msgid "Monday" msgstr "Lunes" #: src/containers/MedicationManagement/index.tsx:54 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:42 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:80 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:118 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:197 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:231 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:302 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:43 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:83 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:123 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:204 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:240 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:312 #: src/containers/PatientDetails/PatientResources/utils.tsx:39 #: src/containers/PatientDetails/PatientResources/utils.tsx:74 #: src/containers/PatientDetails/PatientResources/utils.tsx:113 @@ -812,7 +821,7 @@ msgstr "Nuevo agendamiento" #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:151 #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:176 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:137 -#: src/utils/questionnaire.ts:49 +#: src/utils/questionnaire.ts:63 msgid "No" msgstr "No" @@ -892,7 +901,7 @@ msgid "Order added" msgstr "Orden añadida" #: src/containers/PatientDetails/PatientHeader/index.tsx:82 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:294 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:304 msgid "Orders" msgstr "Resultados" @@ -912,6 +921,7 @@ msgstr "Envase" msgid "Password" msgstr "Contraseña" +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:39 #: src/containers/EncounterList/index.tsx:46 #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:20 #: src/containers/InvoiceList/components/InvoiceListSearchBar/index.tsx:39 @@ -935,8 +945,8 @@ msgstr "Paciente guardado" msgid "Patient successfully created" msgstr "Paciente creado con éxito" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:49 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:55 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:29 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:35 #: src/containers/PatientDetails/PatientHeader/index.tsx:26 #: src/containers/PatientList/index.tsx:59 msgid "Patients" @@ -964,8 +974,8 @@ msgstr "Widget de teléfono" #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:23 #: src/containers/InvoiceList/components/InvoiceListSearchBar/index.tsx:33 #: src/containers/InvoiceList/tableUtils.tsx:114 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:146 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:265 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:152 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:275 #: src/containers/PatientDetails/PatientResources/utils.tsx:210 msgid "Practitioner" msgstr "Profesional" @@ -974,7 +984,7 @@ msgstr "Profesional" msgid "Practitioner successfully created" msgstr "Profesional creado con éxito" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:50 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:30 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:37 #: src/containers/PractitionerList/index.tsx:77 msgid "Practitioners" @@ -984,7 +994,7 @@ msgstr "Profesionales" msgid "Prepare for print" msgstr "Preparar para imprimir" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:63 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:46 #: src/containers/Prescriptions/index.tsx:48 msgid "Prescriptions" msgstr "Prescripciones" @@ -1010,8 +1020,8 @@ msgstr "Cuestionario" msgid "Questionnaire properties" msgstr "Propiedades del cuestionario" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:51 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:56 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:31 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:36 #: src/containers/QuestionnaireList/index.tsx:80 msgid "Questionnaires" msgstr "Cuestionarios" @@ -1028,7 +1038,7 @@ msgstr "Eliminar" msgid "Repeats" msgstr "Repite" -#: src/components/ModalNewEncounter/index.tsx:23 +#: src/components/ModalNewEncounter/index.tsx:24 msgid "Request Appointment" msgstr "Solicitar cita" @@ -1061,7 +1071,7 @@ msgstr "Fila" msgid "Saturday" msgstr "Sábado" -#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:53 +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:127 #: src/containers/EncounterDetails/AIScribe/index.tsx:182 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:130 #: src/containers/QuestionnaireBuilder/QuestionnaireSaveForm/index.tsx:90 @@ -1070,16 +1080,28 @@ msgstr "Sábado" msgid "Save" msgstr "Guardar" +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:77 +msgid "Save as draft" +msgstr "" + #: src/containers/QuestionnaireBuilder/index.tsx:57 #: src/containers/QuestionnaireBuilder/index.tsx:58 msgid "Save questionnaire" msgstr "Guardar cuestionario" +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:94 +msgid "Saved as draft" +msgstr "" + +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:85 +msgid "Saving draft" +msgstr "" + #: src/containers/Scheduling/ScheduleCalendar/index.tsx:41 msgid "Schedule calendar" msgstr "Calendario de programación" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:60 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:43 #: src/containers/OrganizationScheduling/index.tsx:71 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:39 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:71 @@ -1123,8 +1145,8 @@ msgstr "Ver detalles de ValueSet" msgid "Select (default)" msgstr "Seleccionar (predeterminado)" -#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:25 -#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:50 +#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:30 +#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:55 msgid "Select..." msgstr "Seleccionar..." @@ -1132,7 +1154,7 @@ msgstr "Seleccionar..." msgid "Serum creatinin" msgstr "Creatinina sérica" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:269 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:279 msgid "Service" msgstr "Servicio" @@ -1140,7 +1162,7 @@ msgstr "Servicio" msgid "Service Requests" msgstr "Solicitud de servicios" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:47 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:27 msgid "Services" msgstr "Servicios" @@ -1185,6 +1207,7 @@ msgstr "Comenzar" msgid "Start date" msgstr "Fecha de inicio" +#: src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/index.tsx:84 #: src/containers/EncounterDetails/index.tsx:104 msgid "Start scribe" msgstr "Iniciar escritura" @@ -1274,11 +1297,11 @@ msgstr "Texto (por defecto)" msgid "Text with macro" msgstr "Texto con macro" -#: src/containers/App/index.tsx:125 +#: src/containers/App/index.tsx:129 msgid "Thank you for filling out the questionnaire. Now you can close this page." msgstr "Gracias por completar el cuestionario. Ahora puedes cerrar esta página." -#: src/containers/App/index.tsx:124 +#: src/containers/App/index.tsx:128 msgid "Thank you!" msgstr "¡Gracias!" @@ -1311,7 +1334,7 @@ msgstr "Aún no hay documentos" msgid "Thursday" msgstr "Jueves" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:277 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:287 #: src/containers/Scheduling/Availability/index.tsx:129 msgid "Time" msgstr "Hora" @@ -1421,7 +1444,7 @@ msgstr "Bienvenido a" #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:150 #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:175 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:136 -#: src/utils/questionnaire.ts:49 +#: src/utils/questionnaire.ts:63 msgid "Yes" msgstr "Sí" diff --git a/src/locale/ru/messages.po b/src/locale/ru/messages.po index b972f8f9..36c86e9d 100644 --- a/src/locale/ru/messages.po +++ b/src/locale/ru/messages.po @@ -48,12 +48,12 @@ msgstr "" msgid "Active" msgstr "Активный" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:223 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:232 #: src/containers/PatientDetails/PatientResources/utils.tsx:24 msgid "Active Medications" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:157 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:163 msgid "Activities" msgstr "" @@ -128,7 +128,7 @@ msgstr "" msgid "Aidbox Forms Builder" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:34 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:35 #: src/containers/PatientDetails/PatientResources/utils.tsx:98 msgid "Allergies" msgstr "" @@ -225,7 +225,7 @@ msgstr "" msgid "Calories" msgstr "" -#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:43 +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:117 #: src/containers/DocumentsList/ChooseDocumentToCreateModal/index.tsx:54 #: src/containers/InvoiceList/components/ModalCancelInvoice/index.tsx:18 #: src/containers/Prescriptions/ModalMedicationRequestCancel.tsx:22 @@ -247,6 +247,7 @@ msgstr "" msgid "Cancelled" msgstr "" +#: src/components/AudioRecorder/index.tsx:30 #: src/containers/EncounterDetails/AIScribe/index.tsx:93 msgid "Capture in progress" msgstr "" @@ -255,6 +256,10 @@ msgstr "" msgid "Characteristics" msgstr "" +#: src/components/BaseQuestionnaireResponseForm/widgets/UploadFileControl/index.tsx:29 +msgid "Click or drag file to this area to upload" +msgstr "" + #: src/containers/PractitionerDetails/PractitionerOverview/index.tsx:92 msgid "Clinician successfully updated" msgstr "" @@ -295,7 +300,7 @@ msgstr "" msgid "Concepts:" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:72 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:75 #: src/containers/PatientDetails/PatientResources/utils.tsx:59 msgid "Conditions" msgstr "" @@ -308,7 +313,7 @@ msgstr "" msgid "Confirm Medication Request" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:110 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:115 #: src/containers/PatientDetails/PatientResources/utils.tsx:172 msgid "Consents" msgstr "" @@ -337,9 +342,9 @@ msgstr "" msgid "Create document" msgstr "" -#: src/components/ModalNewEncounter/index.tsx:22 -#: src/components/ModalNewEncounter/index.tsx:24 +#: src/components/ModalNewEncounter/index.tsx:23 #: src/components/ModalNewEncounter/index.tsx:25 +#: src/components/ModalNewEncounter/index.tsx:26 msgid "Create Encounter" msgstr "" @@ -368,11 +373,11 @@ msgstr "" #: src/containers/EncounterList/index.tsx:64 #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:26 #: src/containers/InvoiceList/tableUtils.tsx:129 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:53 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:91 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:136 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:208 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:273 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:55 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:95 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:142 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:216 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:283 #: src/containers/PatientDetails/PatientResources/utils.tsx:85 #: src/containers/PatientDetails/PatientResources/utils.tsx:124 #: src/containers/PatientDetails/PatientResources/utils.tsx:163 @@ -452,7 +457,7 @@ msgstr "Документы" msgid "Documents have been successfully extracted" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:242 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:252 #: src/containers/PatientDetails/PatientResources/utils.tsx:50 msgid "Dosage" msgstr "" @@ -527,7 +532,7 @@ msgstr "" msgid "Encounter completed" msgstr "" -#: src/components/ModalNewEncounter/index.tsx:38 +#: src/components/ModalNewEncounter/index.tsx:39 msgid "Encounter successfully created" msgstr "" @@ -535,8 +540,8 @@ msgstr "" msgid "Encounter was successfully completed" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:48 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:54 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:28 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:34 #: src/components/PatientEncounter/index.tsx:79 #: src/containers/EncounterList/index.tsx:103 #: src/containers/PatientDetails/PatientHeader/index.tsx:79 @@ -672,7 +677,7 @@ msgstr "" msgid "IL-6" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:189 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:196 #: src/containers/PatientDetails/PatientResources/utils.tsx:137 msgid "Immunization" msgstr "" @@ -689,7 +694,7 @@ msgstr "" msgid "Inline options?" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:307 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:317 msgid "Intent" msgstr "" @@ -701,9 +706,9 @@ msgstr "" msgid "Invoice was successfully payed" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:46 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:58 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:61 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:26 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:40 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:44 #: src/containers/InvoiceList/index.tsx:44 msgid "Invoices" msgstr "" @@ -728,6 +733,10 @@ msgstr "" msgid "Last name" msgstr "" +#: src/components/AudioRecorder/index.tsx:58 +msgid "Listen to the audio" +msgstr "" + #: src/containers/SignIn/index.tsx:89 msgid "Log in" msgstr "" @@ -740,7 +749,7 @@ msgstr "" #~ msgid "Log in as Practitioner" #~ msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarBottom/index.tsx:96 +#: src/components/BaseLayout/Sidebar/SidebarBottom/context.tsx:42 msgid "Log out" msgstr "Выйти" @@ -777,7 +786,7 @@ msgstr "" msgid "Medication request successfully confirmed" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:62 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:45 #: src/containers/MedicationManagement/index.tsx:28 msgid "Medications" msgstr "" @@ -795,12 +804,12 @@ msgid "Monday" msgstr "" #: src/containers/MedicationManagement/index.tsx:54 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:42 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:80 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:118 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:197 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:231 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:302 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:43 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:83 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:123 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:204 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:240 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:312 #: src/containers/PatientDetails/PatientResources/utils.tsx:39 #: src/containers/PatientDetails/PatientResources/utils.tsx:74 #: src/containers/PatientDetails/PatientResources/utils.tsx:113 @@ -824,7 +833,7 @@ msgstr "" #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:151 #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:176 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:137 -#: src/utils/questionnaire.ts:49 +#: src/utils/questionnaire.ts:63 msgid "No" msgstr "" @@ -908,7 +917,7 @@ msgid "Order added" msgstr "" #: src/containers/PatientDetails/PatientHeader/index.tsx:82 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:294 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:304 msgid "Orders" msgstr "" @@ -928,6 +937,7 @@ msgstr "" msgid "Password" msgstr "" +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:39 #: src/containers/EncounterList/index.tsx:46 #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:20 #: src/containers/InvoiceList/components/InvoiceListSearchBar/index.tsx:39 @@ -951,8 +961,8 @@ msgstr "Пациент сохранен" msgid "Patient successfully created" msgstr "Пациент успешно создан" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:49 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:55 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:29 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:35 #: src/containers/PatientDetails/PatientHeader/index.tsx:26 #: src/containers/PatientList/index.tsx:59 msgid "Patients" @@ -980,8 +990,8 @@ msgstr "" #: src/containers/InvoiceDetails/components/InvoiceDetailsHeader/index.tsx:23 #: src/containers/InvoiceList/components/InvoiceListSearchBar/index.tsx:33 #: src/containers/InvoiceList/tableUtils.tsx:114 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:146 -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:265 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:152 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:275 #: src/containers/PatientDetails/PatientResources/utils.tsx:210 msgid "Practitioner" msgstr "Врач" @@ -990,7 +1000,7 @@ msgstr "Врач" msgid "Practitioner successfully created" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:50 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:30 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:37 #: src/containers/PractitionerList/index.tsx:77 msgid "Practitioners" @@ -1000,7 +1010,7 @@ msgstr "Врачи" msgid "Prepare for print" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:63 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:46 #: src/containers/Prescriptions/index.tsx:48 msgid "Prescriptions" msgstr "" @@ -1026,8 +1036,8 @@ msgstr "Опросник" msgid "Questionnaire properties" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:51 -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:56 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:31 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:36 #: src/containers/QuestionnaireList/index.tsx:80 msgid "Questionnaires" msgstr "Опросники" @@ -1044,7 +1054,7 @@ msgstr "" msgid "Repeats" msgstr "" -#: src/components/ModalNewEncounter/index.tsx:23 +#: src/components/ModalNewEncounter/index.tsx:24 msgid "Request Appointment" msgstr "" @@ -1081,7 +1091,7 @@ msgstr "" msgid "Saturday" msgstr "" -#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:53 +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:127 #: src/containers/EncounterDetails/AIScribe/index.tsx:182 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:130 #: src/containers/QuestionnaireBuilder/QuestionnaireSaveForm/index.tsx:90 @@ -1090,16 +1100,28 @@ msgstr "" msgid "Save" msgstr "Сохранить" +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:77 +msgid "Save as draft" +msgstr "" + #: src/containers/QuestionnaireBuilder/index.tsx:57 #: src/containers/QuestionnaireBuilder/index.tsx:58 msgid "Save questionnaire" msgstr "" +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:94 +msgid "Saved as draft" +msgstr "" + +#: src/components/BaseQuestionnaireResponseForm/FormFooter.tsx:85 +msgid "Saving draft" +msgstr "" + #: src/containers/Scheduling/ScheduleCalendar/index.tsx:41 msgid "Schedule calendar" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:60 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:43 #: src/containers/OrganizationScheduling/index.tsx:71 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:39 #: src/containers/PractitionerDetails/PractitionerHeader/index.tsx:71 @@ -1143,8 +1165,8 @@ msgstr "" msgid "Select (default)" msgstr "" -#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:25 -#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:50 +#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:30 +#: src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx:55 msgid "Select..." msgstr "" @@ -1152,7 +1174,7 @@ msgstr "" msgid "Serum creatinin" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:269 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:279 msgid "Service" msgstr "" @@ -1160,7 +1182,7 @@ msgstr "" msgid "Service Requests" msgstr "" -#: src/components/BaseLayout/Sidebar/SidebarTop/index.tsx:47 +#: src/components/BaseLayout/Sidebar/SidebarTop/context.tsx:27 msgid "Services" msgstr "" @@ -1205,6 +1227,7 @@ msgstr "" msgid "Start date" msgstr "Начало периода" +#: src/components/BaseQuestionnaireResponseForm/widgets/AudioRecorderUploader/index.tsx:84 #: src/containers/EncounterDetails/index.tsx:104 msgid "Start scribe" msgstr "" @@ -1294,11 +1317,11 @@ msgstr "" msgid "Text with macro" msgstr "" -#: src/containers/App/index.tsx:125 +#: src/containers/App/index.tsx:129 msgid "Thank you for filling out the questionnaire. Now you can close this page." msgstr "" -#: src/containers/App/index.tsx:124 +#: src/containers/App/index.tsx:128 msgid "Thank you!" msgstr "" @@ -1331,7 +1354,7 @@ msgstr "" msgid "Thursday" msgstr "" -#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:277 +#: src/containers/PatientDetails/PatientOverviewDynamic/components/StandardCard/prepare.tsx:287 #: src/containers/Scheduling/Availability/index.tsx:129 msgid "Time" msgstr "" @@ -1441,7 +1464,7 @@ msgstr "" #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:150 #: src/containers/PatientDetails/PatientDocumentDetails/index.tsx:175 #: src/containers/QuestionnaireBuilder/QuestionnaireItemSettings/index.tsx:136 -#: src/utils/questionnaire.ts:49 +#: src/utils/questionnaire.ts:63 msgid "Yes" msgstr ""