@openmrs/esm-form-engine-lib 3.2.0 → 3.3.0
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/dist/openmrs-esm-form-engine-lib.js +1 -1
- package/package.json +4 -6
- package/src/adapters/obs-adapter.test.ts +117 -0
- package/src/adapters/obs-adapter.ts +54 -27
- package/src/api/index.ts +17 -24
- package/src/components/inputs/file/file-thumbnail.component.tsx +55 -0
- package/src/components/inputs/file/file-thumbnail.scss +42 -0
- package/src/components/inputs/file/file.component.tsx +66 -130
- package/src/components/inputs/file/file.scss +8 -88
- package/src/components/inputs/workspace-launcher/workspace-launcher.component.tsx +10 -6
- package/src/components/renderer/field/fieldRenderUtils.test.ts +35 -5
- package/src/components/renderer/field/fieldRenderUtils.ts +10 -4
- package/src/components/renderer/field/form-field-renderer.component.tsx +26 -3
- package/src/components/sidebar/sidebar.scss +2 -3
- package/src/form-engine.component.tsx +6 -1
- package/src/form-engine.scss +1 -1
- package/src/form-engine.test.tsx +6 -3
- package/src/processors/encounter/encounter-form-processor.ts +4 -3
- package/src/processors/encounter/encounter-processor-helper.ts +17 -15
- package/src/registry/registry.ts +2 -1
- package/src/types/domain.ts +9 -9
- package/src/types/index.ts +3 -3
- package/src/components/inputs/file/camera/camera.component.tsx +0 -34
- package/src/components/inputs/file/camera/camera.scss +0 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,p,
|
|
1
|
+
var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,d,p,c,h,m,v,g,b,y,w,_={78008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(177),t.e(899),t.e(759),t.e(72),t.e(467),t.e(306)]).then((()=>()=>t(4306)))},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})}},P={};function j(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,loaded:!1,exports:{}};return _[e].call(t.exports,t,t.exports,j),t.loaded=!0,t.exports}j.m=_,j.c=P,j.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return j.d(r,{a:r}),r},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,j.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);j.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,j.d(o,i),o},j.d=(e,r)=>{for(var t in r)j.o(r,t)&&!j.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},j.f={},j.e=e=>Promise.all(Object.keys(j.f).reduce(((r,t)=>(j.f[t](e,r),r)),[])),j.u=e=>e+".js",j.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),j.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},n="@openmrs/esm-form-engine-lib:",j.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,j.nc&&a.setAttribute("nonce",j.nc),a.setAttribute("data-webpack",n+o),a.src=e),t[e]=[r];var d=(r,n)=>{a.onerror=a.onload=null,clearTimeout(p);var o=t[e];if(delete t[e],a.parentNode&&a.parentNode.removeChild(a),o&&o.forEach((e=>e(n))),r)return r(n)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=d.bind(null,a.onerror),a.onload=d.bind(null,a.onload),l&&document.head.appendChild(a)}},j.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},j.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),j.j=719,(()=>{j.S={};var e={},r={};j.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];j.o(j.S,t)||(j.S[t]={});var i=j.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","7.0.1-pre.3293",(()=>Promise.all([j.e(177),j.e(387),j.e(899),j.e(72),j.e(467),j.e(766),j.e(310)]).then((()=>()=>j(96387))))),l("dayjs","1.11.13",(()=>j.e(353).then((()=>()=>j(74353))))),l("i18next","23.16.0",(()=>j.e(635).then((()=>()=>j(72635))))),l("react-i18next","11.18.6",(()=>Promise.all([j.e(72),j.e(414)]).then((()=>()=>j(93414))))),l("react","18.3.1",(()=>j.e(540).then((()=>()=>j(96540))))),l("swr/immutable","2.3.3",(()=>Promise.all([j.e(177),j.e(72),j.e(606)]).then((()=>()=>j(54225))))),l("swr/infinite","2.3.3",(()=>Promise.all([j.e(177),j.e(72),j.e(422)]).then((()=>()=>j(23041)))))),e[t]=s.length?Promise.all(s).then((()=>e[t]=1)):1}}})(),(()=>{var e;j.g.importScripts&&(e=j.g.location+"");var r=j.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(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(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),j.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,d=a<e.length?(typeof e[a])[0]:"";if(i>=r.length||"o"==(f=(typeof(u=r[i]))[0]))return!s||("u"==d?a>t&&!n:""==d!=n);if("u"==f){if(!s||"u"!=d)return!1}else if(s)if(d==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"!=d&&"n"!=d){if(n||a<=t)return!1;s=!1,a--}else{if(a<=t||f<d!=n)return!1;s=!1}else"s"!=d&&"n"!=d&&(s=!1,a--)}}var p=[],c=p.pop.bind(p);for(i=1;i<e.length;i++){var h=e[i];p.push(1==h?c()|c():2==h?c()&c():h?l(h,r):!c())}return!!c()},s=(e,r)=>e&&j.o(e,r),u=e=>(e.loaded=1,e.get()),f=e=>Object.keys(e).reduce(((r,t)=>(e[t].eager&&(r[t]=e[t]),r)),{}),d=(e,r,t)=>{var n=t?f(e[r]):e[r];return Object.keys(n).reduce(((e,r)=>!e||!n[e].loaded&&i(e,r)?r:e),0)},p=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+a(n)+")",c=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},m=(e,r,t)=>t?t():((e,r)=>c("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(e=>function(r,t,n,o,i){var a=j.I(r);return a&&a.then&&!n?a.then(e.bind(e,r,j.S[r],t,!1,o,i)):e(r,j.S[r],t,n,o,i)})(((e,r,t,n,o,i)=>{if(!s(r,t))return m(e,t,i);var a=d(r,t,n);return l(o,a)||h(p(r,t,a,o)),u(r[t][a])})),g={},b={16072:()=>v("default","react",!1,[1,18],(()=>j.e(540).then((()=>()=>j(96540))))),15847:()=>v("default","@openmrs/esm-framework",!1,[1,8],(()=>Promise.all([j.e(177),j.e(387),j.e(766)]).then((()=>()=>j(96387))))),53941:()=>v("default","react-i18next",!1,[1,11],(()=>j.e(33).then((()=>()=>j(93414))))),76766:()=>v("default","i18next",!1,[1,23],(()=>j.e(635).then((()=>()=>j(72635))))),44209:()=>v("default","swr/immutable",!1,[1,2],(()=>Promise.all([j.e(177),j.e(225)]).then((()=>()=>j(54225))))),56339:()=>v("default","swr/infinite",!1,[1,2],(()=>Promise.all([j.e(177),j.e(41)]).then((()=>()=>j(23041))))),70231:()=>v("default","dayjs",!1,[1,1],(()=>j.e(353).then((()=>()=>j(74353)))))},y={72:[16072],306:[44209,56339,70231],310:[44209,56339,70231],467:[15847,53941],766:[76766]},w={},j.f.consumes=(e,r)=>{j.o(y,e)&&y[e].forEach((e=>{if(j.o(g,e))return r.push(g[e]);if(!w[e]){var t=r=>{g[e]=0,j.m[e]=t=>{delete j.c[e],t.exports=r()}};w[e]=!0;var n=r=>{delete g[e],j.m[e]=t=>{throw delete j.c[e],r}};try{var o=b[e]();o.then?r.push(g[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}}))},(()=>{var e={719:0};j.f.j=(r,t)=>{var n=j.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else if(/^(467|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=j.p+j.u(r),a=new Error;j.l(i,(t=>{if(j.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)j.o(a,n)&&(j.m[n]=a[n]);l&&l(j)}for(r&&r(t);s<i.length;s++)o=i[s],j.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))})(),j.nc=void 0;var S=j(78008);_openmrs_esm_form_engine_lib=S})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-form-engine-lib",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "React Form Engine for O3",
|
|
5
5
|
"browser": "dist/openmrs-esm-form-engine-lib.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -39,8 +39,7 @@
|
|
|
39
39
|
"react-webcam": "^7.2.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@openmrs/esm-framework": "
|
|
43
|
-
"@openmrs/esm-patient-common-lib": "10.x",
|
|
42
|
+
"@openmrs/esm-framework": "8.x",
|
|
44
43
|
"dayjs": "1.x",
|
|
45
44
|
"i18next": "23.x",
|
|
46
45
|
"react": "18.x",
|
|
@@ -48,9 +47,8 @@
|
|
|
48
47
|
"swr": "2.x"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
51
|
-
"@openmrs/esm-framework": "
|
|
52
|
-
"@openmrs/esm-
|
|
53
|
-
"@openmrs/esm-styleguide": "7.0.0",
|
|
50
|
+
"@openmrs/esm-framework": "next",
|
|
51
|
+
"@openmrs/esm-styleguide": "next",
|
|
54
52
|
"@swc/cli": "^0.1.65",
|
|
55
53
|
"@swc/core": "^1.5.7",
|
|
56
54
|
"@swc/jest": "^0.2.36",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getAttachmentByUuid } from '@openmrs/esm-framework';
|
|
1
2
|
import { type FormContextProps } from '../provider/form-provider';
|
|
2
3
|
import { type FormField } from '../types';
|
|
3
4
|
import { findObsByFormField, hasPreviousObsValueChanged, ObsAdapter } from './obs-adapter';
|
|
@@ -40,6 +41,9 @@ const formContext = {
|
|
|
40
41
|
setDeletedFields: jest.fn(),
|
|
41
42
|
} as FormContextProps;
|
|
42
43
|
|
|
44
|
+
window.openmrsBase = 'openmrs';
|
|
45
|
+
const mockGetAttachmentByUuid = jest.mocked(getAttachmentByUuid);
|
|
46
|
+
|
|
43
47
|
describe('ObsAdapter - transformFieldValue', () => {
|
|
44
48
|
// new submission (enter mode)
|
|
45
49
|
it('should handle submission for text input', () => {
|
|
@@ -188,6 +192,42 @@ describe('ObsAdapter - transformFieldValue', () => {
|
|
|
188
192
|
});
|
|
189
193
|
});
|
|
190
194
|
|
|
195
|
+
it('should handle value transformation for file input', () => {
|
|
196
|
+
// setup
|
|
197
|
+
const field: FormField = {
|
|
198
|
+
label: "Upload an image or use this device's camera to capture an image",
|
|
199
|
+
type: 'obs',
|
|
200
|
+
questionOptions: {
|
|
201
|
+
rendering: 'file',
|
|
202
|
+
},
|
|
203
|
+
meta: {},
|
|
204
|
+
id: 'demoFile',
|
|
205
|
+
};
|
|
206
|
+
// value
|
|
207
|
+
const image = {
|
|
208
|
+
base64Content: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoA',
|
|
209
|
+
capturedFromWebcam: true,
|
|
210
|
+
fileDescription: '',
|
|
211
|
+
fileName: 'Image taken from camera.png',
|
|
212
|
+
fileType: 'image',
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// replay
|
|
216
|
+
const obs = ObsAdapter.transformFieldValue(field, [image], formContext);
|
|
217
|
+
// verify
|
|
218
|
+
expect(obs).toEqual([
|
|
219
|
+
{
|
|
220
|
+
base64Content: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoA',
|
|
221
|
+
capturedFromWebcam: true,
|
|
222
|
+
fileDescription: '',
|
|
223
|
+
fileName: 'Image taken from camera.png',
|
|
224
|
+
fileType: 'image',
|
|
225
|
+
formFieldNamespace: 'rfe-forms',
|
|
226
|
+
formFieldPath: 'rfe-forms-demoFile',
|
|
227
|
+
},
|
|
228
|
+
]);
|
|
229
|
+
});
|
|
230
|
+
|
|
191
231
|
// editing existing values (edit mode)
|
|
192
232
|
it('should edit obs text/number value in edit mode', () => {
|
|
193
233
|
// setup
|
|
@@ -519,6 +559,39 @@ describe('ObsAdapter - transformFieldValue', () => {
|
|
|
519
559
|
});
|
|
520
560
|
expect(field.meta.submission.newValue).toBe(null);
|
|
521
561
|
});
|
|
562
|
+
|
|
563
|
+
it('should void deleted files', () => {
|
|
564
|
+
// setup
|
|
565
|
+
const field: FormField = {
|
|
566
|
+
label: "Upload an image or use this device's camera to capture an image",
|
|
567
|
+
type: 'obs',
|
|
568
|
+
questionOptions: {
|
|
569
|
+
rendering: 'file',
|
|
570
|
+
},
|
|
571
|
+
meta: {
|
|
572
|
+
initialValue: {
|
|
573
|
+
omrsObject: {
|
|
574
|
+
uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
|
|
575
|
+
formFieldPath: 'rfe-forms-demoFile',
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
id: 'demoFile',
|
|
580
|
+
};
|
|
581
|
+
// replay
|
|
582
|
+
ObsAdapter.transformFieldValue(
|
|
583
|
+
field,
|
|
584
|
+
[{ uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3', voided: true }],
|
|
585
|
+
formContext,
|
|
586
|
+
);
|
|
587
|
+
// verify
|
|
588
|
+
expect(field.meta.submission.voidedValue).toEqual([
|
|
589
|
+
{
|
|
590
|
+
uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
|
|
591
|
+
voided: true,
|
|
592
|
+
},
|
|
593
|
+
]);
|
|
594
|
+
});
|
|
522
595
|
});
|
|
523
596
|
|
|
524
597
|
describe('ObsAdapter - getInitialValue', () => {
|
|
@@ -663,6 +736,49 @@ describe('ObsAdapter - getInitialValue', () => {
|
|
|
663
736
|
expect(initialValue).toEqual('12f7be3d-fb5d-47dc-b5e3-56c501be80a6');
|
|
664
737
|
});
|
|
665
738
|
|
|
739
|
+
it('should get initial value for file rendering', async () => {
|
|
740
|
+
// setup
|
|
741
|
+
const field: FormField = {
|
|
742
|
+
label: "Upload an image or use this device's camera to capture an image",
|
|
743
|
+
type: 'obs',
|
|
744
|
+
questionOptions: {
|
|
745
|
+
rendering: 'file',
|
|
746
|
+
},
|
|
747
|
+
meta: {},
|
|
748
|
+
id: 'demoFile',
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
const obs = {
|
|
752
|
+
uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
|
|
753
|
+
formFieldPath: 'rfe-forms-demoFile',
|
|
754
|
+
};
|
|
755
|
+
formContext.domainObjectValue['obs'].push(obs);
|
|
756
|
+
mockGetAttachmentByUuid.mockReturnValue(
|
|
757
|
+
Promise.resolve({
|
|
758
|
+
data: {
|
|
759
|
+
uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
|
|
760
|
+
dateTime: '2025-08-21T19:31:39.000+0000',
|
|
761
|
+
filename: 'image.png',
|
|
762
|
+
comment: 'An image captured for test purposes',
|
|
763
|
+
bytesMimeType: 'image/png',
|
|
764
|
+
bytesContentFamily: 'IMAGE',
|
|
765
|
+
},
|
|
766
|
+
} as any),
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
// replay
|
|
770
|
+
const initialValue = await ObsAdapter.getInitialValue(field, formContext.domainObjectValue, formContext);
|
|
771
|
+
expect(initialValue).toEqual([
|
|
772
|
+
{
|
|
773
|
+
uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
|
|
774
|
+
base64Content: 'openmrs/ws/rest/v1/attachment/6ff51289-b334-4050-8a0e-28cb2034bfc3/bytes',
|
|
775
|
+
fileName: 'image.png',
|
|
776
|
+
fileDescription: 'An image captured for test purposes',
|
|
777
|
+
fileType: 'image',
|
|
778
|
+
},
|
|
779
|
+
]);
|
|
780
|
+
});
|
|
781
|
+
|
|
666
782
|
it('should get initial values for obs-group members', async () => {
|
|
667
783
|
// setup
|
|
668
784
|
const basePath = 'rfe-forms-';
|
|
@@ -952,6 +1068,7 @@ describe('findObsByFormField', () => {
|
|
|
952
1068
|
it('Should find observation by field path', () => {
|
|
953
1069
|
// do find
|
|
954
1070
|
let matchedObs = findObsByFormField(obsList, [], fields[0]);
|
|
1071
|
+
|
|
955
1072
|
// verify
|
|
956
1073
|
expect(matchedObs.length).toBe(1);
|
|
957
1074
|
expect(matchedObs[0]).toBe(obsList[0]);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import dayjs from 'dayjs';
|
|
2
2
|
import { ConceptTrue, codedTypes } from '../constants';
|
|
3
3
|
import {
|
|
4
|
+
type Attachment,
|
|
4
5
|
type OpenmrsObs,
|
|
5
6
|
type FormField,
|
|
6
7
|
type OpenmrsEncounter,
|
|
7
|
-
type AttachmentResponse,
|
|
8
|
-
type Attachment,
|
|
9
8
|
type ValueAndDisplay,
|
|
10
9
|
type FormFieldValueAdapter,
|
|
11
10
|
} from '../types';
|
|
@@ -19,8 +18,7 @@ import {
|
|
|
19
18
|
} from '../utils/common-utils';
|
|
20
19
|
import { type FormContextProps } from '../provider/form-provider';
|
|
21
20
|
import { isEmpty } from '../validators/form-validator';
|
|
22
|
-
import { getAttachmentByUuid } from '
|
|
23
|
-
import { formatDate, type OpenmrsResource, restBaseUrl } from '@openmrs/esm-framework';
|
|
21
|
+
import { attachmentUrl, getAttachmentByUuid, type OpenmrsResource } from '@openmrs/esm-framework';
|
|
24
22
|
|
|
25
23
|
// Temporarily holds observations that have already been bound with matching fields
|
|
26
24
|
export let assignedObsIds: string[] = [];
|
|
@@ -28,15 +26,11 @@ export let assignedObsIds: string[] = [];
|
|
|
28
26
|
export const ObsAdapter: FormFieldValueAdapter = {
|
|
29
27
|
async getInitialValue(field: FormField, sourceObject: any, context: FormContextProps) {
|
|
30
28
|
const encounter = sourceObject ?? (context.domainObjectValue as OpenmrsEncounter);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// TODO: This seems like a violation of the data model.
|
|
35
|
-
// I think we should instead use something like `formFieldPath` to do the mapping.
|
|
36
|
-
const rawAttachment = attachmentsResponse.results?.find((attachment) => attachment.comment === field.id);
|
|
37
|
-
return rawAttachment ? generateAttachment(rawAttachment) : null;
|
|
29
|
+
const matchingObs = findObsByFormField(flattenObsList(encounter.obs), assignedObsIds, field);
|
|
30
|
+
if (hasRendering(field, 'file') && matchingObs?.length) {
|
|
31
|
+
return resolveAttachmentsFromObs(field, matchingObs);
|
|
38
32
|
}
|
|
39
|
-
return extractFieldValue(field,
|
|
33
|
+
return extractFieldValue(field, matchingObs, true);
|
|
40
34
|
},
|
|
41
35
|
async getPreviousValue(field: FormField, sourceObject: any, context: FormContextProps): Promise<ValueAndDisplay> {
|
|
42
36
|
const encounter = sourceObject ?? (context.previousDomainObjectValue as OpenmrsEncounter);
|
|
@@ -85,6 +79,9 @@ export const ObsAdapter: FormFieldValueAdapter = {
|
|
|
85
79
|
if (hasRendering(field, 'checkbox')) {
|
|
86
80
|
return handleMultiSelect(field, Array.isArray(value) ? value : [value]);
|
|
87
81
|
}
|
|
82
|
+
if (hasRendering(field, 'file')) {
|
|
83
|
+
return handleAttachments(field, value);
|
|
84
|
+
}
|
|
88
85
|
if (!isEmpty(value) && hasPreviousObsValueChanged(field, value)) {
|
|
89
86
|
return gracefullySetSubmission(field, editObs(field, value), undefined);
|
|
90
87
|
}
|
|
@@ -251,6 +248,20 @@ function handleMultiSelect(field: FormField, values: Array<string> = []) {
|
|
|
251
248
|
);
|
|
252
249
|
}
|
|
253
250
|
|
|
251
|
+
function handleAttachments(field: FormField, attachments: Attachment[] = []) {
|
|
252
|
+
const voided = attachments
|
|
253
|
+
.filter((attachment) => attachment.uuid && attachment.voided)
|
|
254
|
+
.map((voided) => ({ uuid: voided.uuid, voided: true }));
|
|
255
|
+
const newAttachments = (field.meta.submission.newValue = attachments
|
|
256
|
+
.filter((attachment) => !attachment.uuid)
|
|
257
|
+
.map((newAttachment) => ({
|
|
258
|
+
formFieldNamespace: 'rfe-forms',
|
|
259
|
+
formFieldPath: `rfe-forms-${field.id}`,
|
|
260
|
+
...newAttachment,
|
|
261
|
+
})));
|
|
262
|
+
return gracefullySetSubmission(field, newAttachments, voided);
|
|
263
|
+
}
|
|
264
|
+
|
|
254
265
|
/**
|
|
255
266
|
* Retrieves a list of observations from a given `obsList` that correspond to the specified field.
|
|
256
267
|
*
|
|
@@ -263,9 +274,15 @@ export function findObsByFormField(
|
|
|
263
274
|
claimedObsIds: string[],
|
|
264
275
|
field: FormField,
|
|
265
276
|
): OpenmrsObs[] {
|
|
266
|
-
const obs = obsList.filter(
|
|
267
|
-
|
|
268
|
-
|
|
277
|
+
const obs = obsList.filter((candidate) => {
|
|
278
|
+
// we ignore the concept for attachments because they're managed from the backend
|
|
279
|
+
if (hasRendering(field, 'file') && candidate.formFieldPath == `rfe-forms-${field.id}`) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
return (
|
|
283
|
+
candidate.formFieldPath == `rfe-forms-${field.id}` && candidate.concept.uuid == field.questionOptions.concept
|
|
284
|
+
);
|
|
285
|
+
});
|
|
269
286
|
|
|
270
287
|
// We shall fall back to mapping by the associated concept
|
|
271
288
|
// That being said, we shall find all matching obs and pick the one that wasn't previously claimed.
|
|
@@ -277,17 +294,27 @@ export function findObsByFormField(
|
|
|
277
294
|
return obs;
|
|
278
295
|
}
|
|
279
296
|
|
|
280
|
-
function
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
async function resolveAttachmentsFromObs(field: FormField, obs: OpenmrsObs[]) {
|
|
298
|
+
const abortController = new AbortController();
|
|
299
|
+
const attachments = await Promise.all(
|
|
300
|
+
obs.map((obs) =>
|
|
301
|
+
getAttachmentByUuid(obs.uuid, abortController)
|
|
302
|
+
.then((response) => response.data)
|
|
303
|
+
.catch((error) => {
|
|
304
|
+
console.error(`Failed to fetch attachment ${obs.uuid}:`, error);
|
|
305
|
+
return null;
|
|
306
|
+
}),
|
|
307
|
+
),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
field.meta.initialValue = {
|
|
311
|
+
omrsObject: obs,
|
|
292
312
|
};
|
|
313
|
+
return attachments.filter(Boolean).map((attachment) => ({
|
|
314
|
+
uuid: attachment.uuid,
|
|
315
|
+
base64Content: `${window.openmrsBase}${attachmentUrl}/${attachment.uuid}/bytes`,
|
|
316
|
+
fileName: attachment.filename,
|
|
317
|
+
fileDescription: attachment.comment,
|
|
318
|
+
fileType: attachment.bytesContentFamily?.toLowerCase(),
|
|
319
|
+
})) as Attachment[];
|
|
293
320
|
}
|
package/src/api/index.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { fhirBaseUrl, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
1
|
+
import { attachmentUrl, fhirBaseUrl, openmrsFetch, restBaseUrl, type UploadedFile } from '@openmrs/esm-framework';
|
|
2
2
|
import { encounterRepresentation } from '../constants';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
AttachmentFieldValue,
|
|
5
|
+
FHIRObsResource,
|
|
6
|
+
OpenmrsForm,
|
|
7
|
+
PatientIdentifier,
|
|
8
|
+
PatientProgramPayload,
|
|
9
|
+
} from '../types';
|
|
4
10
|
import { isUuid } from '../utils/boolean-utils';
|
|
5
11
|
|
|
6
12
|
export function saveEncounter(abortController: AbortController, payload, encounterUuid?: string) {
|
|
@@ -18,41 +24,28 @@ export function saveEncounter(abortController: AbortController, payload, encount
|
|
|
18
24
|
});
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
export function
|
|
22
|
-
const url = `${restBaseUrl}/attachment`;
|
|
23
|
-
|
|
24
|
-
const content = field.meta.submission?.newValue?.value;
|
|
25
|
-
const cameraUploadType = typeof content === 'string' && content?.split(';')[0].split(':')[1].split('/')[1];
|
|
26
|
-
|
|
27
|
+
export async function createAttachment(patientUuid: string, encounterUUID: string, attachment: AttachmentFieldValue) {
|
|
27
28
|
const formData = new FormData();
|
|
28
|
-
const fileCaption = field.id;
|
|
29
29
|
|
|
30
|
-
formData.append('fileCaption',
|
|
30
|
+
formData.append('fileCaption', attachment.fileDescription);
|
|
31
31
|
formData.append('patient', patientUuid);
|
|
32
32
|
|
|
33
|
-
if (
|
|
34
|
-
formData.append('file',
|
|
33
|
+
if (attachment.file) {
|
|
34
|
+
formData.append('file', attachment.file, attachment.fileName);
|
|
35
35
|
} else {
|
|
36
|
-
formData.append('file', new File([''],
|
|
37
|
-
formData.append('base64Content',
|
|
36
|
+
formData.append('file', new File([''], attachment.fileName), attachment.fileName);
|
|
37
|
+
formData.append('base64Content', attachment.base64Content);
|
|
38
38
|
}
|
|
39
39
|
formData.append('encounter', encounterUUID);
|
|
40
|
-
formData.append('
|
|
40
|
+
formData.append('formFieldNamespace', attachment.formFieldNamespace);
|
|
41
|
+
formData.append('formFieldPath', attachment.formFieldPath);
|
|
41
42
|
|
|
42
|
-
return openmrsFetch(
|
|
43
|
+
return openmrsFetch(`${attachmentUrl}`, {
|
|
43
44
|
method: 'POST',
|
|
44
|
-
signal: abortController.signal,
|
|
45
45
|
body: formData,
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export function getAttachmentByUuid(patientUuid: string, encounterUuid: string, abortController: AbortController) {
|
|
50
|
-
const attachmentUrl = `${restBaseUrl}/attachment`;
|
|
51
|
-
return openmrsFetch(`${attachmentUrl}?patient=${patientUuid}&encounter=${encounterUuid}`, {
|
|
52
|
-
signal: abortController.signal,
|
|
53
|
-
}).then((response) => response.data);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
49
|
export function getConcept(conceptUuid: string, v: string) {
|
|
57
50
|
return openmrsFetch(`${restBaseUrl}/concept/${conceptUuid}?v=${v}`).then(({ data }) => data.results);
|
|
58
51
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import styles from './file-thumbnail.scss';
|
|
3
|
+
import { CloseFilled, DocumentPdf, DocumentUnknown } from '@carbon/react/icons';
|
|
4
|
+
import { Button } from '@carbon/react';
|
|
5
|
+
|
|
6
|
+
interface FileThumbnailProps {
|
|
7
|
+
src: string;
|
|
8
|
+
title: string;
|
|
9
|
+
bytesContentFamily: string;
|
|
10
|
+
removeFileCb: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ThumbnailProps = Omit<FileThumbnailProps, 'bytesContentFamily' | 'removeFileCb'>;
|
|
14
|
+
|
|
15
|
+
export function FileThumbnail({ bytesContentFamily, removeFileCb, ...thumbnailProps }: FileThumbnailProps) {
|
|
16
|
+
const Thumbnail = useMemo(() => {
|
|
17
|
+
switch (bytesContentFamily) {
|
|
18
|
+
case 'image':
|
|
19
|
+
return ImageThumbnail;
|
|
20
|
+
case 'pdf':
|
|
21
|
+
return PDFThumbnail;
|
|
22
|
+
default:
|
|
23
|
+
return OtherThumbnail;
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className={styles.thumbnail}>
|
|
29
|
+
<Thumbnail {...thumbnailProps} />
|
|
30
|
+
<Button kind="ghost" className={styles.removeButton} onClick={removeFileCb}>
|
|
31
|
+
<CloseFilled size={16} className={styles.closeIcon} />
|
|
32
|
+
</Button>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ImageThumbnail(props: ThumbnailProps) {
|
|
38
|
+
return <img className={styles.imageThumbnail} src={props.src} alt={props.title} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function PDFThumbnail(props: ThumbnailProps) {
|
|
42
|
+
return (
|
|
43
|
+
<div className={styles.pdfThumbnail} role="button" tabIndex={0}>
|
|
44
|
+
<DocumentPdf size={24} />
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function OtherThumbnail(props: ThumbnailProps) {
|
|
50
|
+
return (
|
|
51
|
+
<div className={styles.pdfThumbnail} role="button" tabIndex={0}>
|
|
52
|
+
<DocumentUnknown size={24} />
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@openmrs/esm-styleguide/src/vars' as vars;
|
|
3
|
+
|
|
4
|
+
.thumbnail {
|
|
5
|
+
height: 7rem;
|
|
6
|
+
width: 7rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.imageThumbnail {
|
|
10
|
+
max-width: 100%;
|
|
11
|
+
max-height: 100%;
|
|
12
|
+
object-fit: cover;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.pdfThumbnail {
|
|
16
|
+
background-color: vars.$color-gray-30;
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
align-items: center;
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.removeButton {
|
|
25
|
+
position: absolute;
|
|
26
|
+
top: layout.$spacing-02;
|
|
27
|
+
right: layout.$spacing-02;
|
|
28
|
+
background-color: transparent;
|
|
29
|
+
border: none;
|
|
30
|
+
padding: 0;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
padding-block-start: 0;
|
|
33
|
+
--cds-layout-size-height-local: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.removeButton:hover {
|
|
37
|
+
background-color: transparent;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.closeIcon {
|
|
41
|
+
fill: white !important;
|
|
42
|
+
}
|