@openmrs/esm-form-engine-lib 2.1.0-pre.1422 → 2.1.0-pre.1433
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/2e36cddf3c16560f/2e36cddf3c16560f.gz +0 -0
- package/352ae296f62fb1cd/352ae296f62fb1cd.gz +0 -0
- package/__mocks__/forms/rfe-forms/conditional-required-form.json +2 -3
- package/__mocks__/forms/rfe-forms/labour_and_delivery_test_form.json +1 -4
- package/dist/openmrs-esm-form-engine-lib.js +1 -1
- package/jest.config.js +1 -1
- package/package.json +1 -1
- package/src/components/inputs/select/dropdown.component.tsx +1 -1
- package/src/components/inputs/unspecified/unspecified.component.tsx +2 -2
- package/src/components/processor-factory/form-processor-factory.component.tsx +1 -5
- package/src/components/renderer/custom-hooks-renderer.component.tsx +2 -2
- package/src/components/renderer/field/form-field-renderer.component.tsx +6 -5
- package/src/form-engine.component.tsx +2 -2
- package/src/form-engine.test.tsx +127 -86
- package/src/hooks/useFormStateHelpers.ts +3 -2
- package/src/hooks/useInitialValues.ts +1 -1
- package/src/hooks/useProcessorDependencies.ts +4 -2
- package/src/processors/encounter/encounter-form-processor.ts +9 -6
- package/src/provider/form-factory-helper.ts +2 -1
- package/src/registry/registry.ts +2 -1
- package/src/utils/form-helper.ts +25 -2
- package/9404313828ba927f/9404313828ba927f.gz +0 -0
- package/eabd666b13965980/eabd666b13965980.gz +0 -0
- /package/{f2584189f4831e5f/f2584189f4831e5f.gz → 16563207957244dc/16563207957244dc.gz} +0 -0
- /package/{64c3acd62414b0f6/64c3acd62414b0f6.gz → 57b838e736d9d290/57b838e736d9d290.gz} +0 -0
Binary file
|
Binary file
|
@@ -52,8 +52,7 @@
|
|
52
52
|
"concept": "dc1942b2-5e50-4adc-949d-ad6c905f054e"
|
53
53
|
},
|
54
54
|
"validators": [],
|
55
|
-
"hide": {
|
56
|
-
}
|
55
|
+
"hide": {}
|
57
56
|
},
|
58
57
|
{
|
59
58
|
"label": "If Unscheduled, actual text area scheduled date",
|
@@ -278,4 +277,4 @@
|
|
278
277
|
"version": "1.0",
|
279
278
|
"description": "des",
|
280
279
|
"encounterType": "181820aa-88c9-479b-9077-af92f5364329"
|
281
|
-
}
|
280
|
+
}
|
@@ -328,10 +328,7 @@
|
|
328
328
|
"readonly": "true",
|
329
329
|
"questionOptions": {
|
330
330
|
"concept": "164803AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
331
|
-
"rendering": "text"
|
332
|
-
"calculate": {
|
333
|
-
"calculateExpression": "!isEmpty('MotherPtracker_id') ? myValue = customGenerateInfantPTrackerId(node.value.id, MotherPtracker_id) : ''"
|
334
|
-
}
|
331
|
+
"rendering": "text"
|
335
332
|
},
|
336
333
|
"behaviours": [
|
337
334
|
{
|
@@ -1 +1 @@
|
|
1
|
-
var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,p,d,c,h,m,v,g,b={8008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(901),t.e(
|
1
|
+
var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,p,d,c,h,m,v,g,b={8008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(901),t.e(501),t.e(72),t.e(346),t.e(572)]).then((()=>()=>t(8572)))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then((()=>{throw new Error('Module "'+e+'" does not exist in container.')})),t.R=void 0,r),i=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>i})}},y={};function w(e){var r=y[e];if(void 0!==r)return r.exports;var t=y[e]={id:e,loaded:!1,exports:{}};return b[e].call(t.exports,t,t.exports,w),t.loaded=!0,t.exports}w.m=b,w.c=y,w.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return w.d(r,{a:r}),r},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,w.t=function(t,n){if(1&n&&(t=this(t)),8&n)return t;if("object"==typeof t&&t){if(4&n&&t.__esModule)return t;if(16&n&&"function"==typeof t.then)return t}var o=Object.create(null);w.r(o);var i={};e=e||[null,r({}),r([]),r(r)];for(var a=2&n&&t;"object"==typeof a&&!~e.indexOf(a);a=r(a))Object.getOwnPropertyNames(a).forEach((e=>i[e]=()=>t[e]));return i.default=()=>t,w.d(o,i),o},w.d=(e,r)=>{for(var t in r)w.o(r,t)&&!w.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},w.f={},w.e=e=>Promise.all(Object.keys(w.f).reduce(((r,t)=>(w.f[t](e,r),r)),[])),w.u=e=>e+".js",w.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),w.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},n="@openmrs/esm-form-engine-lib:",w.l=(e,r,o,i)=>{if(t[e])t[e].push(r);else{var a,l;if(void 0!==o)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var f=s[u];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==n+o){a=f;break}}a||(l=!0,(a=document.createElement("script")).charset="utf-8",a.timeout=120,w.nc&&a.setAttribute("nonce",w.nc),a.setAttribute("data-webpack",n+o),a.src=e),t[e]=[r];var p=(r,n)=>{a.onerror=a.onload=null,clearTimeout(d);var o=t[e];if(delete t[e],a.parentNode&&a.parentNode.removeChild(a),o&&o.forEach((e=>e(n))),r)return r(n)},d=setTimeout(p.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=p.bind(null,a.onerror),a.onload=p.bind(null,a.onload),l&&document.head.appendChild(a)}},w.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},w.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{w.S={};var e={},r={};w.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];w.o(w.S,t)||(w.S[t]={});var i=w.S[t],a="@openmrs/esm-form-engine-lib",l=(e,r,t,n)=>{var o=i[e]=i[e]||{},l=o[r];(!l||!l.loaded&&(!n!=!l.eager?n:a>l.from))&&(o[r]={get:t,from:a,eager:!!n})},s=[];return"default"===t&&(l("@openmrs/esm-framework","5.7.3-pre.2161",(()=>Promise.all([w.e(151),w.e(72),w.e(766)]).then((()=>()=>w(5151))))),l("@openmrs/esm-patient-common-lib","8.1.1-pre.5183",(()=>Promise.all([w.e(617),w.e(901),w.e(72),w.e(465),w.e(346),w.e(3)]).then((()=>()=>w(8617))))),l("dayjs","1.11.11",(()=>w.e(353).then((()=>()=>w(4353))))),l("i18next","23.11.4",(()=>w.e(635).then((()=>()=>w(2635))))),l("react-i18next","11.18.6",(()=>Promise.all([w.e(422),w.e(72)]).then((()=>()=>w(4422))))),l("react","18.3.1",(()=>w.e(540).then((()=>()=>w(6540))))),l("swr/_internal","2.2.5",(()=>Promise.all([w.e(993),w.e(72)]).then((()=>()=>w(4993))))),l("swr/immutable","2.2.5",(()=>Promise.all([w.e(225),w.e(72),w.e(465)]).then((()=>()=>w(4225))))),l("swr/infinite","2.2.5",(()=>Promise.all([w.e(41),w.e(72),w.e(465)]).then((()=>()=>w(3041)))))),e[t]=s.length?Promise.all(s).then((()=>e[t]=1)):1}}})(),(()=>{var e;w.g.importScripts&&(e=w.g.location+"");var r=w.g.document;if(!e&&r&&(r.currentScript&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),w.p=e})(),o=e=>{var r=e=>e.split(".").map((e=>+e==e?+e:e)),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},i=(e,r)=>{e=o(e),r=o(r);for(var t=0;;){if(t>=e.length)return t<r.length&&"u"!=(typeof r[t])[0];var n=e[t],i=(typeof n)[0];if(t>=r.length)return"u"==i;var a=r[t],l=(typeof a)[0];if(i!=l)return"o"==i&&"n"==l||"s"==l||"u"==i;if("o"!=i&&"u"!=i&&n!=a)return n<a;t++}},a=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,o=1;o<e.length;o++)n--,t+="u"==(typeof(l=e[o]))[0]?"-":(n>0?".":"")+(n=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+s()+")":1===l?"("+s()+" || "+s()+")":2===l?i.pop()+" "+i.pop():a(l))}return s();function s(){return i.pop().replace(/^\((.+)\)$/,"$1")}},l=(e,r)=>{if(0 in e){r=o(r);var t=e[0],n=t<0;n&&(t=-t-1);for(var i=0,a=1,s=!0;;a++,i++){var u,f,p=a<e.length?(typeof e[a])[0]:"";if(i>=r.length||"o"==(f=(typeof(u=r[i]))[0]))return!s||("u"==p?a>t&&!n:""==p!=n);if("u"==f){if(!s||"u"!=p)return!1}else if(s)if(p==f)if(a<=t){if(u!=e[a])return!1}else{if(n?u>e[a]:u<e[a])return!1;u!=e[a]&&(s=!1)}else if("s"!=p&&"n"!=p){if(n||a<=t)return!1;s=!1,a--}else{if(a<=t||f<p!=n)return!1;s=!1}else"s"!=p&&"n"!=p&&(s=!1,a--)}}var d=[],c=d.pop.bind(d);for(i=1;i<e.length;i++){var h=e[i];d.push(1==h?c()|c():2==h?c()&c():h?l(h,r):!c())}return!!c()},s=(e,r)=>{var t=e[r];return Object.keys(t).reduce(((e,r)=>!e||!t[e].loaded&&i(e,r)?r:e),0)},u=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+a(n)+")",f=(e,r,t,n)=>{var o=s(e,t);return l(n,o)||p(u(e,t,o,n)),d(e[t][o])},p=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},d=e=>(e.loaded=1,e.get()),c=(e=>function(r,t,n,o){var i=w.I(r);return i&&i.then?i.then(e.bind(e,r,w.S[r],t,n,o)):e(0,w.S[r],t,n,o)})(((e,r,t,n,o)=>r&&w.o(r,t)?f(r,0,t,n):o())),h={},m={6072:()=>c("default","react",[1,18],(()=>w.e(540).then((()=>()=>w(6540))))),6766:()=>c("default","i18next",[1,23],(()=>w.e(635).then((()=>()=>w(2635))))),8465:()=>c("default","swr/_internal",[1,2],(()=>w.e(993).then((()=>()=>w(4993))))),3941:()=>c("default","react-i18next",[1,11],(()=>w.e(422).then((()=>()=>w(4422))))),6656:()=>c("default","@openmrs/esm-patient-common-lib",[1,8],(()=>Promise.all([w.e(617),w.e(465)]).then((()=>()=>w(8617))))),4209:()=>c("default","swr/immutable",[1,2],(()=>Promise.all([w.e(225),w.e(465)]).then((()=>()=>w(4225))))),5972:()=>c("default","@openmrs/esm-framework",[1,5],(()=>Promise.all([w.e(151),w.e(766)]).then((()=>()=>w(5151))))),231:()=>c("default","dayjs",[1,1],(()=>w.e(353).then((()=>()=>w(4353))))),6339:()=>c("default","swr/infinite",[1,2],(()=>Promise.all([w.e(41),w.e(465)]).then((()=>()=>w(3041)))))},v={3:[4209,5972],72:[6072],346:[3941,6656],465:[8465],572:[231,4209,5972,6339],766:[6766]},g={},w.f.consumes=(e,r)=>{w.o(v,e)&&v[e].forEach((e=>{if(w.o(h,e))return r.push(h[e]);if(!g[e]){var t=r=>{h[e]=0,w.m[e]=t=>{delete w.c[e],t.exports=r()}};g[e]=!0;var n=r=>{delete h[e],w.m[e]=t=>{throw delete w.c[e],r}};try{var o=m[e]();o.then?r.push(h[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}}))},(()=>{var e={719:0};w.f.j=(r,t)=>{var n=w.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else if(/^(346|465|72|766)$/.test(r))e[r]=0;else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var i=w.p+w.u(r),a=new Error;w.l(i,(t=>{if(w.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;a.message="Loading chunk "+r+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,n[1](a)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[i,a,l]=t,s=0;if(i.some((r=>0!==e[r]))){for(n in a)w.o(a,n)&&(w.m[n]=a[n]);l&&l(w)}for(r&&r(t);s<i.length;s++)o=i[s],w.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=globalThis.webpackChunk_openmrs_esm_form_engine_lib=globalThis.webpackChunk_openmrs_esm_form_engine_lib||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),w.nc=void 0;var _=w(8008);_openmrs_esm_form_engine_lib=_})();
|
package/jest.config.js
CHANGED
@@ -20,7 +20,7 @@ module.exports = {
|
|
20
20
|
'^dexie$': require.resolve('dexie'),
|
21
21
|
'\\.(s?css)$': 'identity-obj-proxy',
|
22
22
|
'@openmrs/esm-framework': '@openmrs/esm-framework/mock',
|
23
|
-
'react-i18next': '<rootDir>/__mocks__/react-i18next.js',
|
23
|
+
'^react-i18next$': '<rootDir>/__mocks__/react-i18next.js',
|
24
24
|
'lodash-es': 'lodash',
|
25
25
|
'react-markdown': '<rootDir>/__mocks__/react-markdown.tsx',
|
26
26
|
'^uuid$': '<rootDir>/node_modules/uuid/dist/index.js',
|
package/package.json
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, {
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
2
2
|
import { useTranslation } from 'react-i18next';
|
3
3
|
import { Dropdown as DropdownInput, Layer } from '@carbon/react';
|
4
4
|
import { shouldUseInlineLayout } from '../../../utils/form-helper';
|
@@ -29,10 +29,10 @@ const UnspecifiedField: React.FC<UnspecifiedFieldProps> = ({ field, fieldValue,
|
|
29
29
|
}, []);
|
30
30
|
|
31
31
|
useEffect(() => {
|
32
|
-
if (field.meta.submission?.newValue) {
|
32
|
+
if (field.meta.submission?.unspecified && field.meta.submission.newValue) {
|
33
33
|
setIsUnspecified(false);
|
34
34
|
field.meta.submission.unspecified = false;
|
35
|
-
updateFormField(
|
35
|
+
updateFormField(field);
|
36
36
|
}
|
37
37
|
}, [field.meta?.submission]);
|
38
38
|
|
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import useProcessorDependencies from '../../hooks/useProcessorDependencies';
|
3
3
|
import useInitialValues from '../../hooks/useInitialValues';
|
4
4
|
import { FormRenderer } from '../renderer/form/form-renderer.component';
|
5
|
-
import { type
|
5
|
+
import { type FormProcessorContextProps, type FormSchema } from '../../types';
|
6
6
|
import { CustomHooksRenderer } from '../renderer/custom-hooks-renderer.component';
|
7
7
|
import { useFormFields } from '../../hooks/useFormFields';
|
8
8
|
import { useConcepts } from '../../hooks/useConcepts';
|
@@ -31,9 +31,6 @@ const FormProcessorFactory = ({
|
|
31
31
|
|
32
32
|
const processor = useMemo(() => {
|
33
33
|
const ProcessorClass = formProcessors[formJson.processor];
|
34
|
-
if (processor) {
|
35
|
-
return processor;
|
36
|
-
}
|
37
34
|
if (ProcessorClass) {
|
38
35
|
return new ProcessorClass(formJson);
|
39
36
|
}
|
@@ -65,7 +62,6 @@ const FormProcessorFactory = ({
|
|
65
62
|
const useCustomHooks = processor.getCustomHooks().useCustomHooks;
|
66
63
|
const [isLoadingCustomHooks, setIsLoadingCustomHooks] = useState(!!useCustomHooks);
|
67
64
|
const [isLoadingProcessorDependencies, setIsLoadingProcessorDependencies] = useState(true);
|
68
|
-
|
69
65
|
const {
|
70
66
|
isLoadingInitialValues,
|
71
67
|
initialValues,
|
@@ -9,11 +9,11 @@ export const CustomHooksRenderer = ({
|
|
9
9
|
}: {
|
10
10
|
context: FormProcessorContextProps;
|
11
11
|
setContext: (context: FormProcessorContextProps) => void;
|
12
|
-
useCustomHooks: (context: FormProcessorContextProps) => {
|
12
|
+
useCustomHooks: (context: Partial<FormProcessorContextProps>) => {
|
13
13
|
data: any;
|
14
14
|
isLoading: boolean;
|
15
15
|
error: any;
|
16
|
-
updateContext: (setContext:
|
16
|
+
updateContext: (setContext: React.Dispatch<React.SetStateAction<FormProcessorContextProps>>) => void;
|
17
17
|
};
|
18
18
|
setIsLoadingCustomHooks: (isLoading: boolean) => void;
|
19
19
|
}) => {
|
@@ -1,17 +1,18 @@
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react';
|
2
2
|
import {
|
3
3
|
type FormField,
|
4
|
-
type
|
5
|
-
type ValidationResult,
|
4
|
+
type FormFieldInputProps,
|
6
5
|
type FormFieldValidator,
|
6
|
+
type FormFieldValueAdapter,
|
7
|
+
type RenderType,
|
7
8
|
type SessionMode,
|
9
|
+
type ValidationResult,
|
8
10
|
type ValueAndDisplay,
|
9
11
|
} from '../../../types';
|
10
12
|
import { Controller, useWatch } from 'react-hook-form';
|
11
13
|
import { ToastNotification } from '@carbon/react';
|
12
14
|
import { useTranslation } from 'react-i18next';
|
13
15
|
import { ErrorBoundary } from 'react-error-boundary';
|
14
|
-
import { type FormFieldValueAdapter, type FormFieldInputProps } from '../../../types';
|
15
16
|
import { hasRendering } from '../../../utils/common-utils';
|
16
17
|
import { useFormProviderContext } from '../../../provider/form-provider';
|
17
18
|
import { isEmpty } from '../../../validators/form-validator';
|
@@ -112,7 +113,7 @@ export const FormFieldRenderer = ({ fieldId, valueAdapter, repeatOptions }: Form
|
|
112
113
|
value,
|
113
114
|
formFieldValidators,
|
114
115
|
{
|
115
|
-
|
116
|
+
formFields: formFields,
|
116
117
|
values: getValues(),
|
117
118
|
expressionContext: { patient, mode: sessionMode },
|
118
119
|
},
|
@@ -217,7 +218,7 @@ function ErrorFallback({ error }) {
|
|
217
218
|
}
|
218
219
|
|
219
220
|
export interface ValidatorConfig {
|
220
|
-
|
221
|
+
formFields: FormField[];
|
221
222
|
values: Record<string, any>;
|
222
223
|
expressionContext: {
|
223
224
|
patient: fhir.Patient;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
2
|
-
import type { FormField,
|
2
|
+
import type { FormField, FormSchema, SessionMode } from './types';
|
3
3
|
import { useSession, type Visit } from '@openmrs/esm-framework';
|
4
4
|
import { useFormJson } from '.';
|
5
5
|
import FormProcessorFactory from './components/processor-factory/form-processor-factory.component';
|
@@ -9,7 +9,7 @@ import { useWorkspaceLayout } from './hooks/useWorkspaceLayout';
|
|
9
9
|
import { FormFactoryProvider } from './provider/form-factory-provider';
|
10
10
|
import classNames from 'classnames';
|
11
11
|
import styles from './form-engine.scss';
|
12
|
-
import {
|
12
|
+
import { Button, ButtonSet, InlineLoading } from '@carbon/react';
|
13
13
|
import { I18nextProvider, useTranslation } from 'react-i18next';
|
14
14
|
import PatientBanner from './components/patient-banner/patient-banner.component';
|
15
15
|
import MarkdownWrapper from './components/inputs/markdown/markdown-wrapper.component';
|
package/src/form-engine.test.tsx
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import dayjs from 'dayjs';
|
3
3
|
import userEvent from '@testing-library/user-event';
|
4
|
-
import { act, cleanup, render, screen, within
|
5
|
-
import {
|
6
|
-
|
4
|
+
import { act, cleanup, render, screen, within } from '@testing-library/react';
|
5
|
+
import {
|
6
|
+
ExtensionSlot,
|
7
|
+
OpenmrsDatePicker,
|
8
|
+
openmrsFetch,
|
9
|
+
restBaseUrl,
|
10
|
+
usePatient,
|
11
|
+
useSession,
|
12
|
+
} from '@openmrs/esm-framework';
|
7
13
|
import { when } from 'jest-when';
|
8
14
|
import * as api from './api';
|
9
15
|
import { assertFormHasAllFields, findMultiSelectInput, findSelectInput } from './utils/test-utils';
|
@@ -11,72 +17,63 @@ import { evaluatePostSubmissionExpression } from './utils/post-submission-action
|
|
11
17
|
import { mockPatient } from '__mocks__/patient.mock';
|
12
18
|
import { mockSessionDataResponse } from '__mocks__/session.mock';
|
13
19
|
import { mockVisit } from '__mocks__/visit.mock';
|
14
|
-
import ageValidationForm from '__mocks__/forms/rfe-forms/age-validation-form.json';
|
15
|
-
import bmiForm from '__mocks__/forms/rfe-forms/bmi-test-form.json';
|
16
|
-
import bsaForm from '__mocks__/forms/rfe-forms/bsa-test-form.json';
|
17
20
|
import demoHtsForm from '__mocks__/forms/rfe-forms/demo_hts-form.json';
|
18
21
|
import demoHtsOpenmrsForm from '__mocks__/forms/afe-forms/demo_hts-form.json';
|
19
|
-
import eddForm from '__mocks__/forms/rfe-forms/edd-test-form.json';
|
20
|
-
import externalDataSourceForm from '__mocks__/forms/rfe-forms/external_data_source_form.json';
|
21
22
|
import filterAnswerOptionsTestForm from '__mocks__/forms/rfe-forms/filter-answer-options-test-form.json';
|
22
23
|
import htsPocForm from '__mocks__/packages/hiv/forms/hts_poc/1.1.json';
|
23
24
|
import labourAndDeliveryTestForm from '__mocks__/forms/rfe-forms/labour_and_delivery_test_form.json';
|
24
25
|
import mockConceptsForm from '__mocks__/concepts.mock.json';
|
25
|
-
import monthsOnArtForm from '__mocks__/forms/rfe-forms/months-on-art-form.json';
|
26
|
-
import nextVisitForm from '__mocks__/forms/rfe-forms/next-visit-test-form.json';
|
27
26
|
import obsGroupTestForm from '__mocks__/forms/rfe-forms/obs-group-test_form.json';
|
28
27
|
import postSubmissionTestForm from '__mocks__/forms/rfe-forms/post-submission-test-form.json';
|
29
28
|
import referenceByMappingForm from '__mocks__/forms/rfe-forms/reference-by-mapping-form.json';
|
30
29
|
import sampleFieldsForm from '__mocks__/forms/rfe-forms/sample_fields.json';
|
31
30
|
import testEnrolmentForm from '__mocks__/forms/rfe-forms/test-enrolment-form.json';
|
32
|
-
import viralLoadStatusForm from '__mocks__/forms/rfe-forms/viral-load-status-form.json';
|
33
31
|
import historicalExpressionsForm from '__mocks__/forms/rfe-forms/historical-expressions-form.json';
|
34
32
|
import mockHxpEncounter from '__mocks__/forms/rfe-forms/mockHistoricalvisitsEncounter.json';
|
35
33
|
import requiredTestForm from '__mocks__/forms/rfe-forms/required-form.json';
|
36
34
|
import conditionalRequiredTestForm from '__mocks__/forms/rfe-forms/conditional-required-form.json';
|
37
35
|
import conditionalAnsweredForm from '__mocks__/forms/rfe-forms/conditional-answered-form.json';
|
36
|
+
import ageValidationForm from '__mocks__/forms/rfe-forms/age-validation-form.json';
|
37
|
+
import bmiForm from '__mocks__/forms/rfe-forms/bmi-test-form.json';
|
38
|
+
import bsaForm from '__mocks__/forms/rfe-forms/bsa-test-form.json';
|
39
|
+
import eddForm from '__mocks__/forms/rfe-forms/edd-test-form.json';
|
40
|
+
import externalDataSourceForm from '__mocks__/forms/rfe-forms/external_data_source_form.json';
|
41
|
+
import monthsOnArtForm from '__mocks__/forms/rfe-forms/months-on-art-form.json';
|
42
|
+
import nextVisitForm from '__mocks__/forms/rfe-forms/next-visit-test-form.json';
|
43
|
+
import viralLoadStatusForm from '__mocks__/forms/rfe-forms/viral-load-status-form.json';
|
44
|
+
|
38
45
|
import FormEngine from './form-engine.component';
|
39
46
|
|
40
47
|
const patientUUID = '8673ee4f-e2ab-4077-ba55-4980f408773e';
|
41
48
|
const visit = mockVisit;
|
42
|
-
const mockOpenmrsFetch = jest.fn();
|
43
49
|
const formsResourcePath = when((url: string) => url.includes(`${restBaseUrl}/form/`));
|
44
|
-
const
|
50
|
+
const clobDataResourcePath = when((url: string) => url.includes(`${restBaseUrl}/clobdata/`));
|
45
51
|
global.ResizeObserver = require('resize-observer-polyfill');
|
46
52
|
|
53
|
+
const mockOpenmrsFetch = jest.mocked(openmrsFetch);
|
54
|
+
const mockExtensionSlot = jest.mocked(ExtensionSlot);
|
55
|
+
const mockUsePatient = jest.mocked(usePatient);
|
56
|
+
const mockUseSession = jest.mocked(useSession);
|
57
|
+
const mockOpenmrsDatePicker = jest.mocked(OpenmrsDatePicker);
|
58
|
+
|
59
|
+
mockOpenmrsDatePicker.mockImplementation(({ id, labelText, value, onChange, isInvalid, invalidText }) => {
|
60
|
+
return (
|
61
|
+
<>
|
62
|
+
<label htmlFor={id}>{labelText}</label>
|
63
|
+
<input
|
64
|
+
id={id}
|
65
|
+
value={value ? dayjs(value as unknown as string).format('DD/MM/YYYY') : ''}
|
66
|
+
onChange={(evt) => {
|
67
|
+
onChange(dayjs(evt.target.value).toDate());
|
68
|
+
}}
|
69
|
+
/>
|
70
|
+
{isInvalid && <span>{invalidText}</span>}
|
71
|
+
</>
|
72
|
+
);
|
73
|
+
});
|
74
|
+
|
47
75
|
when(mockOpenmrsFetch).calledWith(formsResourcePath).mockReturnValue({ data: demoHtsOpenmrsForm });
|
48
|
-
when(mockOpenmrsFetch).calledWith(
|
49
|
-
|
50
|
-
const locale = window.i18next.language == 'en' ? 'en-GB' : window.i18next.language;
|
51
|
-
|
52
|
-
// jest.mock('@openmrs/esm-framework', () => {
|
53
|
-
// const originalModule = jest.requireActual('@openmrs/esm-framework');
|
54
|
-
|
55
|
-
// return {
|
56
|
-
// ...originalModule,
|
57
|
-
// createErrorHandler: jest.fn(),
|
58
|
-
// showNotification: jest.fn(),
|
59
|
-
// showToast: jest.fn(),
|
60
|
-
// getAsyncLifecycle: jest.fn(),
|
61
|
-
// usePatient: jest.fn().mockImplementation(() => ({ patient: mockPatient })),
|
62
|
-
// registerExtension: jest.fn(),
|
63
|
-
// useSession: jest.fn().mockImplementation(() => mockSessionDataResponse.data),
|
64
|
-
// openmrsFetch: jest.fn().mockImplementation((args) => mockOpenmrsFetch(args)),
|
65
|
-
// OpenmrsDatePicker: jest.fn().mockImplementation(({ id, labelText, value, onChange, isInvalid, invalidText }) => {
|
66
|
-
// return (
|
67
|
-
// <>
|
68
|
-
// <label htmlFor={id}>{labelText}</label>
|
69
|
-
// <input
|
70
|
-
// id={id}
|
71
|
-
// value={value ? dayjs(value).format('DD/MM/YYYY') : undefined}
|
72
|
-
// onChange={(evt) => onChange(parseDate(dayjs(evt.target.value).format('YYYY-MM-DD')))}
|
73
|
-
// />
|
74
|
-
// {isInvalid && invalidText && <span>{invalidText}</span>}
|
75
|
-
// </>
|
76
|
-
// );
|
77
|
-
// }),
|
78
|
-
// };
|
79
|
-
// });
|
76
|
+
when(mockOpenmrsFetch).calledWith(clobDataResourcePath).mockReturnValue({ data: demoHtsForm });
|
80
77
|
|
81
78
|
jest.mock('../src/api', () => {
|
82
79
|
const originalModule = jest.requireActual('../src/api');
|
@@ -92,10 +89,54 @@ jest.mock('../src/api', () => {
|
|
92
89
|
});
|
93
90
|
|
94
91
|
jest.mock('./hooks/useRestMaxResultsCount', () => jest.fn().mockReturnValue({ systemSetting: { value: '50' } }));
|
95
|
-
|
96
|
-
|
92
|
+
jest.mock('./hooks/useEncounterRole', () => ({
|
93
|
+
useEncounterRole: jest.fn().mockReturnValue({
|
94
|
+
isLoading: false,
|
95
|
+
encounterRole: { name: 'Clinician', uuid: 'clinician-uuid' },
|
96
|
+
error: undefined,
|
97
|
+
}),
|
98
|
+
}));
|
99
|
+
|
100
|
+
jest.mock('./hooks/useConcepts', () => ({
|
101
|
+
useConcepts: jest.fn().mockImplementation((references: Set<string>) => {
|
102
|
+
if ([...references].join(',').includes('PIH:Occurrence of trauma,PIH:Yes,PIH:No,PIH:COUGH')) {
|
103
|
+
return {
|
104
|
+
isLoading: false,
|
105
|
+
concepts: mockConceptsForm.results,
|
106
|
+
error: undefined,
|
107
|
+
};
|
108
|
+
}
|
109
|
+
return {
|
110
|
+
isLoading: false,
|
111
|
+
concepts: undefined,
|
112
|
+
error: undefined,
|
113
|
+
};
|
114
|
+
}),
|
115
|
+
}));
|
116
|
+
|
117
|
+
describe('Form engine component', () => {
|
97
118
|
const user = userEvent.setup();
|
98
119
|
|
120
|
+
beforeEach(() => {
|
121
|
+
Object.defineProperty(window, 'i18next', {
|
122
|
+
writable: true,
|
123
|
+
configurable: true,
|
124
|
+
value: {
|
125
|
+
language: 'en',
|
126
|
+
t: jest.fn(),
|
127
|
+
},
|
128
|
+
});
|
129
|
+
|
130
|
+
mockExtensionSlot.mockImplementation((ext) => <>{ext.name}</>);
|
131
|
+
mockUsePatient.mockImplementation(() => ({
|
132
|
+
patient: mockPatient,
|
133
|
+
isLoading: false,
|
134
|
+
error: undefined,
|
135
|
+
patientUuid: mockPatient.id,
|
136
|
+
}));
|
137
|
+
mockUseSession.mockImplementation(() => mockSessionDataResponse.data);
|
138
|
+
});
|
139
|
+
|
99
140
|
afterEach(() => {
|
100
141
|
jest.useRealTimers();
|
101
142
|
});
|
@@ -187,23 +228,24 @@ xdescribe('Form engine component', () => {
|
|
187
228
|
name: /reason for hospitalization:/i,
|
188
229
|
});
|
189
230
|
|
190
|
-
expect(hospitalizationHistoryDropdown);
|
191
|
-
expect(hospitalizationReasonDropdown);
|
231
|
+
expect(hospitalizationHistoryDropdown).toBeInTheDocument();
|
232
|
+
expect(hospitalizationReasonDropdown).toBeInTheDocument();
|
192
233
|
|
193
|
-
|
234
|
+
await user.click(hospitalizationHistoryDropdown);
|
194
235
|
|
195
236
|
expect(screen.getByText(/yes/i)).toBeInTheDocument();
|
196
237
|
expect(screen.getByText(/no/i)).toBeInTheDocument();
|
197
238
|
|
198
|
-
|
239
|
+
await user.click(screen.getByRole('option', { name: /no/i }));
|
240
|
+
await user.click(screen.getByText(/No/i));
|
199
241
|
|
200
|
-
|
242
|
+
await user.click(hospitalizationReasonDropdown);
|
201
243
|
|
202
244
|
expect(screen.getByText(/Maternal Visit/i)).toBeInTheDocument();
|
203
245
|
expect(screen.getByText(/Emergency Visit/i)).toBeInTheDocument();
|
204
246
|
expect(screen.getByText(/Unscheduled visit late/i)).toBeInTheDocument();
|
205
247
|
|
206
|
-
|
248
|
+
await user.click(screen.getByText(/Maternal Visit/i));
|
207
249
|
|
208
250
|
const errorMessage = screen.getByText(
|
209
251
|
/Providing diagnosis but didn't answer that patient was hospitalized in question/i,
|
@@ -211,8 +253,8 @@ xdescribe('Form engine component', () => {
|
|
211
253
|
|
212
254
|
expect(errorMessage).toBeInTheDocument();
|
213
255
|
|
214
|
-
|
215
|
-
|
256
|
+
await user.click(hospitalizationHistoryDropdown);
|
257
|
+
await user.click(screen.getByText(/yes/i));
|
216
258
|
|
217
259
|
expect(errorMessage).not.toBeInTheDocument();
|
218
260
|
});
|
@@ -231,11 +273,8 @@ xdescribe('Form engine component', () => {
|
|
231
273
|
expect(api.getPreviousEncounter).toHaveBeenCalled();
|
232
274
|
expect(api.getPreviousEncounter).toHaveReturnedWith(Promise.resolve(mockHxpEncounter));
|
233
275
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
expect(reuseValueButton).toBeInTheDocument;
|
238
|
-
expect(evaluatedHistoricalValue).toBeInTheDocument;
|
276
|
+
expect(screen.getByRole('button', { name: /reuse value/i })).toBeInTheDocument;
|
277
|
+
expect(screen.getByText(/Entry into a country/i));
|
239
278
|
});
|
240
279
|
});
|
241
280
|
|
@@ -306,10 +345,9 @@ xdescribe('Form engine component', () => {
|
|
306
345
|
// const dateInputField = await screen.getByLabelText(/If Unscheduled, actual scheduled date/i);
|
307
346
|
// expect(dateInputField).toHaveClass('cds--date-picker__input--invalid');
|
308
347
|
const errorMessage = await screen.findByText(
|
309
|
-
|
348
|
+
/Patient visit marked as unscheduled. Please provide the scheduled date./i,
|
310
349
|
);
|
311
350
|
expect(errorMessage).toBeInTheDocument();
|
312
|
-
|
313
351
|
// Validate text field
|
314
352
|
const textInputField = screen.getByLabelText(/If Unscheduled, actual text scheduled date/i);
|
315
353
|
expect(textInputField).toHaveClass('cds--text-input--invalid');
|
@@ -618,20 +656,30 @@ xdescribe('Form engine component', () => {
|
|
618
656
|
|
619
657
|
const eddField = screen.getByRole('textbox', { name: /edd/i });
|
620
658
|
const lmpField = screen.getByRole('textbox', { name: /lmp/i });
|
621
|
-
|
622
659
|
await user.click(lmpField);
|
623
660
|
await user.paste('2022-07-06T00:00:00.000Z');
|
624
661
|
await user.tab();
|
625
662
|
|
626
|
-
expect(lmpField).toHaveValue(
|
627
|
-
expect(eddField).toHaveValue(
|
663
|
+
expect(lmpField).toHaveValue('06/07/2022');
|
664
|
+
expect(eddField).toHaveValue('12/04/2023');
|
628
665
|
});
|
629
666
|
|
630
667
|
it('should evaluate months on ART', async () => {
|
631
668
|
await act(async () => renderForm(null, monthsOnArtForm));
|
632
669
|
|
633
|
-
jest
|
634
|
-
|
670
|
+
jest
|
671
|
+
.useFakeTimers({
|
672
|
+
doNotFake: [
|
673
|
+
'nextTick',
|
674
|
+
'setImmediate',
|
675
|
+
'clearImmediate',
|
676
|
+
'setInterval',
|
677
|
+
'clearInterval',
|
678
|
+
'setTimeout',
|
679
|
+
'clearTimeout',
|
680
|
+
],
|
681
|
+
})
|
682
|
+
.setSystemTime(new Date(2022, 9, 1));
|
635
683
|
|
636
684
|
let artStartDateField = screen.getByRole('textbox', {
|
637
685
|
name: /antiretroviral treatment start date/i,
|
@@ -643,12 +691,11 @@ xdescribe('Form engine component', () => {
|
|
643
691
|
expect(artStartDateField).not.toHaveValue();
|
644
692
|
expect(monthsOnArtField).not.toHaveValue();
|
645
693
|
|
646
|
-
|
647
|
-
|
694
|
+
await user.click(artStartDateField);
|
695
|
+
await user.paste('2022-02-05');
|
696
|
+
await user.tab();
|
648
697
|
|
649
|
-
|
650
|
-
expect(monthsOnArtField).toHaveValue(7);
|
651
|
-
});
|
698
|
+
expect(monthsOnArtField).toHaveValue(7);
|
652
699
|
});
|
653
700
|
|
654
701
|
it('should evaluate viral load status', async () => {
|
@@ -664,13 +711,12 @@ xdescribe('Form engine component', () => {
|
|
664
711
|
name: /unsuppressed/i,
|
665
712
|
});
|
666
713
|
|
667
|
-
|
714
|
+
await user.type(viralLoadCountField, '30');
|
715
|
+
await user.tab();
|
668
716
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
expect(unsuppressedField).not.toBeChecked();
|
673
|
-
});
|
717
|
+
expect(viralLoadCountField).toHaveValue(30);
|
718
|
+
expect(suppressedField).toBeChecked();
|
719
|
+
expect(unsuppressedField).not.toBeChecked();
|
674
720
|
});
|
675
721
|
|
676
722
|
it('should only show question when age is under 5', async () => {
|
@@ -689,7 +735,7 @@ xdescribe('Form engine component', () => {
|
|
689
735
|
name: /mrn/i,
|
690
736
|
});
|
691
737
|
|
692
|
-
expect(enrollmentDate).toHaveValue(
|
738
|
+
expect(enrollmentDate).toHaveValue('06/07/1975');
|
693
739
|
|
694
740
|
expect(mrn).toBeVisible();
|
695
741
|
});
|
@@ -701,7 +747,7 @@ xdescribe('Form engine component', () => {
|
|
701
747
|
name: /body weight/i,
|
702
748
|
});
|
703
749
|
|
704
|
-
|
750
|
+
expect(bodyWeightField).toHaveValue(60);
|
705
751
|
});
|
706
752
|
|
707
753
|
it('should evaluate next visit date', async () => {
|
@@ -731,12 +777,6 @@ xdescribe('Form engine component', () => {
|
|
731
777
|
});
|
732
778
|
|
733
779
|
describe('Concept references', () => {
|
734
|
-
const conceptResourcePath = when((url: string) =>
|
735
|
-
url.includes(`${restBaseUrl}/concept?references=PIH:Occurrence of trauma,PIH:Yes,PIH:No,PIH:COUGH`),
|
736
|
-
);
|
737
|
-
|
738
|
-
when(mockOpenmrsFetch).calledWith(conceptResourcePath).mockReturnValue({ data: mockConceptsForm });
|
739
|
-
|
740
780
|
it('should add default labels based on concept display and substitute mapping references with uuids', async () => {
|
741
781
|
await act(async () => renderForm(null, referenceByMappingForm));
|
742
782
|
|
@@ -811,6 +851,7 @@ xdescribe('Form engine component', () => {
|
|
811
851
|
patientUUID={patientUUID}
|
812
852
|
formSessionIntent={intent}
|
813
853
|
visit={visit}
|
854
|
+
mode="enter"
|
814
855
|
/>,
|
815
856
|
);
|
816
857
|
}
|
@@ -1,13 +1,14 @@
|
|
1
1
|
import { type Dispatch, useCallback } from 'react';
|
2
|
-
import { type
|
2
|
+
import { type FormField, type FormSchema } from '../types';
|
3
3
|
import { type Action } from '../components/renderer/form/state';
|
4
|
+
import cloneDeep from 'lodash/cloneDeep';
|
4
5
|
|
5
6
|
export function useFormStateHelpers(dispatch: Dispatch<Action>, formFields: FormField[]) {
|
6
7
|
const addFormField = useCallback((field: FormField) => {
|
7
8
|
dispatch({ type: 'ADD_FORM_FIELD', value: field });
|
8
9
|
}, []);
|
9
10
|
const updateFormField = useCallback((field: FormField) => {
|
10
|
-
dispatch({ type: 'UPDATE_FORM_FIELD', value:
|
11
|
+
dispatch({ type: 'UPDATE_FORM_FIELD', value: cloneDeep(field) });
|
11
12
|
}, []);
|
12
13
|
|
13
14
|
const getFormField = useCallback(
|
@@ -31,7 +31,7 @@ const useInitialValues = (
|
|
31
31
|
setIsLoadingInitialValues(false);
|
32
32
|
});
|
33
33
|
}
|
34
|
-
}, [formProcessor, isLoadingContextDependencies, context]);
|
34
|
+
}, [formProcessor, isLoadingContextDependencies, context, initialValues]);
|
35
35
|
|
36
36
|
return { isLoadingInitialValues, initialValues, error };
|
37
37
|
};
|
@@ -1,6 +1,7 @@
|
|
1
|
-
import {
|
1
|
+
import { useEffect, useState } from 'react';
|
2
2
|
import { type FormProcessorContextProps } from '../types';
|
3
3
|
import { type FormProcessor } from '../processors/form-processor';
|
4
|
+
import { reportError } from '../utils/error-utils';
|
4
5
|
|
5
6
|
const useProcessorDependencies = (
|
6
7
|
formProcessor: FormProcessor,
|
@@ -20,9 +21,10 @@ const useProcessorDependencies = (
|
|
20
21
|
})
|
21
22
|
.catch((error) => {
|
22
23
|
setError(error);
|
24
|
+
reportError(error, 'Encountered error while loading dependencies');
|
23
25
|
});
|
24
26
|
}
|
25
|
-
}, []);
|
27
|
+
}, [loadDependencies]);
|
26
28
|
|
27
29
|
return { isLoading, error };
|
28
30
|
};
|
@@ -1,15 +1,15 @@
|
|
1
1
|
import {
|
2
|
-
type ValueAndDisplay,
|
3
2
|
type FormField,
|
4
|
-
type FormProcessorContextProps,
|
5
3
|
type FormPage,
|
4
|
+
type FormProcessorContextProps,
|
5
|
+
type FormSchema,
|
6
6
|
type FormSection,
|
7
|
+
type ValueAndDisplay,
|
7
8
|
} from '../../types';
|
8
9
|
import { usePatientPrograms } from '../../hooks/usePatientPrograms';
|
9
10
|
import { useEffect, useState } from 'react';
|
10
11
|
import { useEncounter } from '../../hooks/useEncounter';
|
11
12
|
import { isEmpty } from '../../validators/form-validator';
|
12
|
-
import { type FormSchema } from '../../types';
|
13
13
|
import { type FormContextProps } from '../../provider/form-provider';
|
14
14
|
import { FormProcessor } from '../form-processor';
|
15
15
|
import {
|
@@ -28,8 +28,9 @@ import { moduleName } from '../../globals';
|
|
28
28
|
import { extractErrorMessagesFromResponse } from '../../utils/error-utils';
|
29
29
|
import { getPreviousEncounter, saveEncounter } from '../../api';
|
30
30
|
import { useEncounterRole } from '../../hooks/useEncounterRole';
|
31
|
-
import {
|
31
|
+
import { evaluateAsyncExpression, evaluateExpression, type FormNode } from '../../utils/expression-runner';
|
32
32
|
import { hasRendering } from '../../utils/common-utils';
|
33
|
+
import { extractObsValueAndDisplay } from '../../utils/form-helper';
|
33
34
|
|
34
35
|
function useCustomHooks(context: Partial<FormProcessorContextProps>) {
|
35
36
|
const [isLoading, setIsLoading] = useState(true);
|
@@ -77,8 +78,9 @@ const contextInitializableTypes = [
|
|
77
78
|
'encounterLocation',
|
78
79
|
'patientIdentifier',
|
79
80
|
'encounterRole',
|
80
|
-
'programState'
|
81
|
+
'programState',
|
81
82
|
];
|
83
|
+
|
82
84
|
export class EncounterFormProcessor extends FormProcessor {
|
83
85
|
prepareFormSchema(schema: FormSchema) {
|
84
86
|
schema.pages.forEach((page) => {
|
@@ -105,6 +107,7 @@ export class EncounterFormProcessor extends FormProcessor {
|
|
105
107
|
});
|
106
108
|
}
|
107
109
|
}
|
110
|
+
|
108
111
|
return schema;
|
109
112
|
}
|
110
113
|
|
@@ -309,7 +312,7 @@ export class EncounterFormProcessor extends FormProcessor {
|
|
309
312
|
patient: patient,
|
310
313
|
previousEncounter: previousDomainObjectValue,
|
311
314
|
});
|
312
|
-
return value;
|
315
|
+
return extractObsValueAndDisplay(field, value);
|
313
316
|
}
|
314
317
|
if (previousDomainObjectValue && field.questionOptions.enablePreviousValue) {
|
315
318
|
return await adapter.getPreviousValue(field, previousDomainObjectValue, context);
|
@@ -13,6 +13,7 @@ export function validateForm(context: FormContextProps) {
|
|
13
13
|
patient,
|
14
14
|
sessionMode,
|
15
15
|
addInvalidField,
|
16
|
+
updateFormField,
|
16
17
|
methods: { getValues, trigger },
|
17
18
|
} = context;
|
18
19
|
const values = getValues();
|
@@ -34,7 +35,7 @@ export function validateForm(context: FormContextProps) {
|
|
34
35
|
const errors = validationResults.filter((result) => result.resultType === 'error');
|
35
36
|
if (errors.length) {
|
36
37
|
field.meta.submission = { ...field.meta.submission, errors };
|
37
|
-
|
38
|
+
updateFormField(field);
|
38
39
|
addInvalidField(field);
|
39
40
|
}
|
40
41
|
return errors;
|
package/src/registry/registry.ts
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import {
|
2
2
|
type FormField,
|
3
3
|
type DataSource,
|
4
|
+
type FormFieldInputProps,
|
4
5
|
type FormFieldValidator,
|
6
|
+
type FormFieldValueAdapter,
|
5
7
|
type FormSchemaTransformer,
|
6
8
|
type PostSubmissionAction,
|
7
9
|
} from '../types';
|
@@ -13,7 +15,6 @@ import { inbuiltDataSources } from './inbuilt-components/inbuiltDataSources';
|
|
13
15
|
import { getControlTemplate } from './inbuilt-components/control-templates';
|
14
16
|
import { inbuiltPostSubmissionActions } from './inbuilt-components/InbuiltPostSubmissionActions';
|
15
17
|
import { inbuiltFormTransformers } from './inbuilt-components/inbuiltTransformers';
|
16
|
-
import { type FormFieldInputProps, type FormFieldValueAdapter } from '../types';
|
17
18
|
import { inbuiltFieldValueAdapters } from './inbuilt-components/inbuiltFieldValueAdapters';
|
18
19
|
|
19
20
|
/**
|
package/src/utils/form-helper.ts
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
import { type LayoutType } from '@openmrs/esm-framework';
|
2
|
-
import { type FormField, type FormPage, type FormSection, type SessionMode } from '../types';
|
2
|
+
import { type OpenmrsObs, type FormField, type FormPage, type FormSection, type SessionMode } from '../types';
|
3
3
|
import { isEmpty } from '../validators/form-validator';
|
4
|
-
import {
|
4
|
+
import { parseToLocalDateTime } from './common-utils';
|
5
|
+
import dayjs from 'dayjs';
|
5
6
|
|
6
7
|
export function shouldUseInlineLayout(
|
7
8
|
renderingType: 'single-line' | 'multiline' | 'automatic',
|
@@ -170,3 +171,25 @@ export function scrollIntoView(viewId: string, shouldFocus: boolean = false) {
|
|
170
171
|
currentElement?.focus();
|
171
172
|
}
|
172
173
|
}
|
174
|
+
|
175
|
+
export const extractObsValueAndDisplay = (field: FormField, obs: OpenmrsObs) => {
|
176
|
+
const rendering = field.questionOptions.rendering;
|
177
|
+
|
178
|
+
if (typeof obs.value === 'string' || typeof obs.value === 'number') {
|
179
|
+
if (rendering === 'date' || rendering === 'datetime') {
|
180
|
+
const dateObj = parseToLocalDateTime(`${obs.value}`);
|
181
|
+
return { value: dateObj, display: dayjs(dateObj).format('YYYY-MM-DD HH:mm') };
|
182
|
+
}
|
183
|
+
return { value: obs.value, display: obs.value };
|
184
|
+
} else if (['toggle', 'checkbox'].includes(rendering)) {
|
185
|
+
return {
|
186
|
+
value: obs.value?.uuid,
|
187
|
+
display: obs.value?.name?.name,
|
188
|
+
};
|
189
|
+
} else {
|
190
|
+
return {
|
191
|
+
value: obs.value?.uuid,
|
192
|
+
display: field.questionOptions.answers?.find((option) => option.concept === obs.value?.uuid)?.label,
|
193
|
+
};
|
194
|
+
}
|
195
|
+
};
|
Binary file
|
Binary file
|
File without changes
|
File without changes
|