@openmrs/esm-patient-tests-app 11.3.1-pre.9228 → 11.3.1-pre.9254
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/.turbo/turbo-build.log +5 -5
- package/dist/6231.js +1 -1
- package/dist/6231.js.map +1 -1
- package/dist/6301.js +1 -1
- package/dist/6301.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-tests-app.js +1 -1
- package/dist/openmrs-esm-patient-tests-app.js.buildmanifest.json +10 -10
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/test-results/filter/filter-types.ts +19 -0
- package/src/test-results/grouped-timeline/grouped-timeline.test.tsx +49 -0
- package/src/test-results/grouped-timeline/reference-range-helpers.test.ts +272 -0
- package/src/test-results/grouped-timeline/reference-range-helpers.ts +112 -0
- package/src/test-results/grouped-timeline/timeline-data-group.component.tsx +10 -3
- package/src/test-results/grouped-timeline/useObstreeData.ts +68 -4
- package/src/test-results/individual-results-table/individual-results-table.component.tsx +14 -3
- package/src/test-results/individual-results-table/individual-results-table.test.tsx +46 -1
- package/src/test-results/individual-results-table-tablet/lab-set-panel.component.tsx +5 -1
- package/src/test-results/loadPatientTestData/usePatientResultsData.ts +3 -3
- package/src/test-results/trendline/trendline-resource.tsx +48 -7
- package/src/types.ts +11 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
var _openmrs_esm_patient_tests_app;(()=>{"use strict";var e,r,t,n,o,a,i,s,l,u,p,f,c,d,h,m,v,g,b,y,w,_={38584:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(8307),t.e(1778),t.e(1343),t.e(5434),t.e(3205),t.e(6231)]).then((()=>()=>t(26231)))},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),a=(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:()=>a})}},P={};function S(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,exports:{}};return _[e].call(t.exports,t,t.exports,S),t.exports}S.m=_,S.c=P,S.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return S.d(r,{a:r}),r},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,S.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);S.r(o);var a={};e=e||[null,r({}),r([]),r(r)];for(var i=2&n&&t;"object"==typeof i&&!~e.indexOf(i);i=r(i))Object.getOwnPropertyNames(i).forEach((e=>a[e]=()=>t[e]));return a.default=()=>t,S.d(o,a),o},S.d=(e,r)=>{for(var t in r)S.o(r,t)&&!S.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},S.f={},S.e=e=>Promise.all(Object.keys(S.f).reduce(((r,t)=>(S.f[t](e,r),r)),[])),S.u=e=>e+".js",S.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),S.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},n="@openmrs/esm-patient-tests-app:",S.l=(e,r,o,a)=>{if(t[e])t[e].push(r);else{var i,s;if(void 0!==o)for(var l=document.getElementsByTagName("script"),u=0;u<l.length;u++){var p=l[u];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==n+o){i=p;break}}i||(s=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,S.nc&&i.setAttribute("nonce",S.nc),i.setAttribute("data-webpack",n+o),i.src=e),t[e]=[r];var f=(r,n)=>{i.onerror=i.onload=null,clearTimeout(c);var o=t[e];if(delete t[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach((e=>e(n))),r)return r(n)},c=setTimeout(f.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=f.bind(null,i.onerror),i.onload=f.bind(null,i.onload),s&&document.head.appendChild(i)}},S.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{S.S={};var e={},r={};S.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];S.o(S.S,t)||(S.S[t]={});var a=S.S[t],i="@openmrs/esm-patient-tests-app",s=(e,r,t,n)=>{var o=a[e]=a[e]||{},s=o[r];(!s||!s.loaded&&(!n!=!s.eager?n:i>s.from))&&(o[r]={get:t,from:i,eager:!!n})},l=[];return"default"===t&&(s("@openmrs/esm-framework","8.0.1-pre.3473",(()=>Promise.all([S.e(9540),S.e(8307),S.e(5670),S.e(1343),S.e(8010),S.e(5434),S.e(4818),S.e(3205)]).then((()=>()=>S(55670))))),s("@openmrs/esm-patient-common-lib","11.3.1-pre.
|
|
1
|
+
var _openmrs_esm_patient_tests_app;(()=>{"use strict";var e,r,t,n,o,a,i,s,l,u,p,f,c,d,h,m,v,g,b,y,w,_={38584:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(8307),t.e(1778),t.e(1343),t.e(5434),t.e(3205),t.e(6231)]).then((()=>()=>t(26231)))},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),a=(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:()=>a})}},P={};function S(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,exports:{}};return _[e].call(t.exports,t,t.exports,S),t.exports}S.m=_,S.c=P,S.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return S.d(r,{a:r}),r},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,S.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);S.r(o);var a={};e=e||[null,r({}),r([]),r(r)];for(var i=2&n&&t;"object"==typeof i&&!~e.indexOf(i);i=r(i))Object.getOwnPropertyNames(i).forEach((e=>a[e]=()=>t[e]));return a.default=()=>t,S.d(o,a),o},S.d=(e,r)=>{for(var t in r)S.o(r,t)&&!S.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},S.f={},S.e=e=>Promise.all(Object.keys(S.f).reduce(((r,t)=>(S.f[t](e,r),r)),[])),S.u=e=>e+".js",S.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),S.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},n="@openmrs/esm-patient-tests-app:",S.l=(e,r,o,a)=>{if(t[e])t[e].push(r);else{var i,s;if(void 0!==o)for(var l=document.getElementsByTagName("script"),u=0;u<l.length;u++){var p=l[u];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==n+o){i=p;break}}i||(s=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,S.nc&&i.setAttribute("nonce",S.nc),i.setAttribute("data-webpack",n+o),i.src=e),t[e]=[r];var f=(r,n)=>{i.onerror=i.onload=null,clearTimeout(c);var o=t[e];if(delete t[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach((e=>e(n))),r)return r(n)},c=setTimeout(f.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=f.bind(null,i.onerror),i.onload=f.bind(null,i.onload),s&&document.head.appendChild(i)}},S.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{S.S={};var e={},r={};S.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];S.o(S.S,t)||(S.S[t]={});var a=S.S[t],i="@openmrs/esm-patient-tests-app",s=(e,r,t,n)=>{var o=a[e]=a[e]||{},s=o[r];(!s||!s.loaded&&(!n!=!s.eager?n:i>s.from))&&(o[r]={get:t,from:i,eager:!!n})},l=[];return"default"===t&&(s("@openmrs/esm-framework","8.0.1-pre.3473",(()=>Promise.all([S.e(9540),S.e(8307),S.e(5670),S.e(1343),S.e(8010),S.e(5434),S.e(4818),S.e(3205)]).then((()=>()=>S(55670))))),s("@openmrs/esm-patient-common-lib","11.3.1-pre.9254",(()=>Promise.all([S.e(9540),S.e(8307),S.e(1343),S.e(8010),S.e(5434),S.e(4818),S.e(3205),S.e(34),S.e(7473)]).then((()=>()=>S(60034))))),s("react-i18next","16.0.0",(()=>Promise.all([S.e(6336),S.e(1343)]).then((()=>()=>S(16336))))),s("react-router-dom","6.16.0",(()=>Promise.all([S.e(2913),S.e(1343)]).then((()=>()=>S(52913))))),s("react","18.3.1",(()=>S.e(4041).then((()=>()=>S(14041))))),s("rxjs","6.6.7",(()=>S.e(2457).then((()=>()=>S(82457))))),s("swr/_internal","2.2.5",(()=>Promise.all([S.e(2372),S.e(1343)]).then((()=>()=>S(42372))))),s("swr/immutable","2.2.5",(()=>Promise.all([S.e(1343),S.e(5434),S.e(7495)]).then((()=>()=>S(29876))))),s("swr/infinite","2.2.5",(()=>Promise.all([S.e(1343),S.e(5434),S.e(89)]).then((()=>()=>S(52470)))))),e[t]=l.length?Promise.all(l).then((()=>e[t]=1)):1}}})(),(()=>{var e;S.g.importScripts&&(e=S.g.location+"");var r=S.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(/\/[^\/]+$/,"/"),S.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},a=(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],a=(typeof n)[0];if(t>=r.length)return"u"==a;var i=r[t],s=(typeof i)[0];if(a!=s)return"o"==a&&"n"==s||"s"==s||"u"==a;if("o"!=a&&"u"!=a&&n!=i)return n<i;t++}},i=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(s=e[o]))[0]?"-":(n>0?".":"")+(n=2,s);return t}var a=[];for(o=1;o<e.length;o++){var s=e[o];a.push(0===s?"not("+l()+")":1===s?"("+l()+" || "+l()+")":2===s?a.pop()+" "+a.pop():i(s))}return l();function l(){return a.pop().replace(/^\((.+)\)$/,"$1")}},s=(e,r)=>{if(0 in e){r=o(r);var t=e[0],n=t<0;n&&(t=-t-1);for(var a=0,i=1,l=!0;;i++,a++){var u,p,f=i<e.length?(typeof e[i])[0]:"";if(a>=r.length||"o"==(p=(typeof(u=r[a]))[0]))return!l||("u"==f?i>t&&!n:""==f!=n);if("u"==p){if(!l||"u"!=f)return!1}else if(l)if(f==p)if(i<=t){if(u!=e[i])return!1}else{if(n?u>e[i]:u<e[i])return!1;u!=e[i]&&(l=!1)}else if("s"!=f&&"n"!=f){if(n||i<=t)return!1;l=!1,i--}else{if(i<=t||p<f!=n)return!1;l=!1}else"s"!=f&&"n"!=f&&(l=!1,i--)}}var c=[],d=c.pop.bind(c);for(a=1;a<e.length;a++){var h=e[a];c.push(1==h?d()|d():2==h?d()&d():h?s(h,r):!d())}return!!d()},l=(e,r)=>e&&S.o(e,r),u=e=>(e.loaded=1,e.get()),p=e=>Object.keys(e).reduce(((r,t)=>(e[t].eager&&(r[t]=e[t]),r)),{}),f=(e,r,t)=>{var n=t?p(e[r]):e[r];return Object.keys(n).reduce(((e,r)=>!e||!n[e].loaded&&a(e,r)?r:e),0)},c=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+i(n)+")",d=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},m=(e,r,t)=>t?t():((e,r)=>d("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(e=>function(r,t,n,o,a){var i=S.I(r);return i&&i.then&&!n?i.then(e.bind(e,r,S.S[r],t,!1,o,a)):e(r,S.S[r],t,n,o,a)})(((e,r,t,n,o,a)=>{if(!l(r,t))return m(e,t,a);var i=f(r,t,n);return s(o,i)||h(c(r,t,i,o)),u(r[t][i])})),g={},b={1343:()=>v("default","react",!1,[1,18],(()=>S.e(4041).then((()=>()=>S(14041))))),88010:()=>v("default","swr/immutable",!1,[1,2],(()=>S.e(9876).then((()=>()=>S(29876))))),55434:()=>v("default","swr/_internal",!1,[1,2],(()=>S.e(2372).then((()=>()=>S(42372))))),24818:()=>v("default","rxjs",!1,[1,6],(()=>S.e(2457).then((()=>()=>S(82457))))),8877:()=>v("default","react-router-dom",!1,[1,6],(()=>S.e(2913).then((()=>()=>S(52913))))),54440:()=>v("default","@openmrs/esm-framework",!1,[1,8],(()=>Promise.all([S.e(9540),S.e(5670),S.e(8010),S.e(4818)]).then((()=>()=>S(55670))))),72339:()=>v("default","react-i18next",!1,[1,16],(()=>S.e(6336).then((()=>()=>S(16336))))),98394:()=>v("default","swr/infinite",!1,[1,2],(()=>S.e(2470).then((()=>()=>S(52470))))),36274:()=>v("default","@openmrs/esm-patient-common-lib",!1,[1,11],(()=>Promise.all([S.e(9540),S.e(8010),S.e(4818),S.e(34)]).then((()=>()=>S(60034)))))},y={1343:[1343],3205:[8877,54440,72339,98394],4818:[24818],5434:[55434],6231:[36274],8010:[88010]},w={},S.f.consumes=(e,r)=>{S.o(y,e)&&y[e].forEach((e=>{if(S.o(g,e))return r.push(g[e]);if(!w[e]){var t=r=>{g[e]=0,S.m[e]=t=>{delete S.c[e],t.exports=r()}};w[e]=!0;var n=r=>{delete g[e],S.m[e]=t=>{throw delete S.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={7773:0};S.f.j=(r,t)=>{var n=S.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else if(/^(1343|3205|4818|5434|8010)$/.test(r))e[r]=0;else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var a=S.p+S.u(r),i=new Error;S.l(a,(t=>{if(S.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+o+": "+a+")",i.name="ChunkLoadError",i.type=o,i.request=a,n[1](i)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[a,i,s]=t,l=0;if(a.some((r=>0!==e[r]))){for(n in i)S.o(i,n)&&(S.m[n]=i[n]);s&&s(S)}for(r&&r(t);l<a.length;l++)o=a[l],S.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=globalThis.webpackChunk_openmrs_esm_patient_tests_app=globalThis.webpackChunk_openmrs_esm_patient_tests_app||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),S.nc=void 0;var j=S(38584);_openmrs_esm_patient_tests_app=j})();
|
|
@@ -954,9 +954,9 @@
|
|
|
954
954
|
"initial": false,
|
|
955
955
|
"entry": false,
|
|
956
956
|
"recorded": false,
|
|
957
|
-
"size":
|
|
957
|
+
"size": 738827,
|
|
958
958
|
"sizes": {
|
|
959
|
-
"javascript":
|
|
959
|
+
"javascript": 738785,
|
|
960
960
|
"consume-shared": 42
|
|
961
961
|
},
|
|
962
962
|
"names": [],
|
|
@@ -970,7 +970,7 @@
|
|
|
970
970
|
"auxiliaryFiles": [
|
|
971
971
|
"6231.js.map"
|
|
972
972
|
],
|
|
973
|
-
"hash": "
|
|
973
|
+
"hash": "949981401b6085d5",
|
|
974
974
|
"childrenByOrder": {}
|
|
975
975
|
},
|
|
976
976
|
{
|
|
@@ -978,9 +978,9 @@
|
|
|
978
978
|
"initial": false,
|
|
979
979
|
"entry": false,
|
|
980
980
|
"recorded": false,
|
|
981
|
-
"size":
|
|
981
|
+
"size": 76000,
|
|
982
982
|
"sizes": {
|
|
983
|
-
"javascript":
|
|
983
|
+
"javascript": 76000
|
|
984
984
|
},
|
|
985
985
|
"names": [],
|
|
986
986
|
"idHints": [],
|
|
@@ -994,7 +994,7 @@
|
|
|
994
994
|
"auxiliaryFiles": [
|
|
995
995
|
"6301.js.map"
|
|
996
996
|
],
|
|
997
|
-
"hash": "
|
|
997
|
+
"hash": "8d3927897dbb65e8",
|
|
998
998
|
"childrenByOrder": {}
|
|
999
999
|
},
|
|
1000
1000
|
{
|
|
@@ -1277,7 +1277,7 @@
|
|
|
1277
1277
|
"auxiliaryFiles": [
|
|
1278
1278
|
"openmrs-esm-patient-tests-app.js.map"
|
|
1279
1279
|
],
|
|
1280
|
-
"hash": "
|
|
1280
|
+
"hash": "8f003e89f6fbda3d",
|
|
1281
1281
|
"childrenByOrder": {}
|
|
1282
1282
|
},
|
|
1283
1283
|
{
|
|
@@ -1399,10 +1399,10 @@
|
|
|
1399
1399
|
"initial": true,
|
|
1400
1400
|
"entry": true,
|
|
1401
1401
|
"recorded": false,
|
|
1402
|
-
"size":
|
|
1402
|
+
"size": 3736150,
|
|
1403
1403
|
"sizes": {
|
|
1404
1404
|
"consume-shared": 294,
|
|
1405
|
-
"javascript":
|
|
1405
|
+
"javascript": 3712938,
|
|
1406
1406
|
"share-init": 378,
|
|
1407
1407
|
"runtime": 22540
|
|
1408
1408
|
},
|
|
@@ -1419,7 +1419,7 @@
|
|
|
1419
1419
|
"auxiliaryFiles": [
|
|
1420
1420
|
"main.js.map"
|
|
1421
1421
|
],
|
|
1422
|
-
"hash": "
|
|
1422
|
+
"hash": "978f1142f406f9ed",
|
|
1423
1423
|
"childrenByOrder": {}
|
|
1424
1424
|
},
|
|
1425
1425
|
{
|
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":">=2.2.0"},"extensions":[{"name":"test-results-summary-dashboard","component":"testResultsDashboardLink","slot":"patient-chart-dashboard-slot","meta":{"slot":"patient-chart-test-results-dashboard-slot","path":"Results","hideDashboardTitle":true,"layoutMode":"anchored"},"order":4},{"name":"test-results-filtered-overview","slot":"test-results-filtered-overview-slot","component":"externalOverview"},{"name":"results-viewer","slots":["patient-chart-results-viewer-slot","patient-chart-test-results-dashboard-slot"],"component":"resultsViewer"},{"name":"lab-order-panel","component":"labOrderPanel","slot":"order-basket-slot","order":2}],"modals":[{"name":"timeline-results-modal","component":"timelineResultsModal"},{"name":"print-modal","component":"printModal"},{"name":"edit-lab-results-modal","component":"editLabResultsModal"}],"workspaces":[{"name":"add-lab-order","type":"order","component":"addLabOrderWorkspace","title":"addLabOrderWorkspaceTitle"}],"version":"11.3.1-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":">=2.2.0"},"extensions":[{"name":"test-results-summary-dashboard","component":"testResultsDashboardLink","slot":"patient-chart-dashboard-slot","meta":{"slot":"patient-chart-test-results-dashboard-slot","path":"Results","hideDashboardTitle":true,"layoutMode":"anchored"},"order":4},{"name":"test-results-filtered-overview","slot":"test-results-filtered-overview-slot","component":"externalOverview"},{"name":"results-viewer","slots":["patient-chart-results-viewer-slot","patient-chart-test-results-dashboard-slot"],"component":"resultsViewer"},{"name":"lab-order-panel","component":"labOrderPanel","slot":"order-basket-slot","order":2}],"modals":[{"name":"timeline-results-modal","component":"timelineResultsModal"},{"name":"print-modal","component":"printModal"},{"name":"edit-lab-results-modal","component":"editLabResultsModal"}],"workspaces":[{"name":"add-lab-order","type":"order","component":"addLabOrderWorkspace","title":"addLabOrderWorkspaceTitle"}],"version":"11.3.1-pre.9254"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-patient-tests-app",
|
|
3
|
-
"version": "11.3.1-pre.
|
|
3
|
+
"version": "11.3.1-pre.9254",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Patient test order and results microfrontend for the OpenMRS SPA",
|
|
6
6
|
"browser": "dist/openmrs-esm-patient-tests-app.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"swr": "2.x"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@openmrs/esm-patient-common-lib": "11.3.1-pre.
|
|
56
|
+
"@openmrs/esm-patient-common-lib": "11.3.1-pre.9254",
|
|
57
57
|
"webpack": "^5.99.9"
|
|
58
58
|
},
|
|
59
59
|
"stableVersion": "11.3.0"
|
|
@@ -14,6 +14,7 @@ export interface TreeNode {
|
|
|
14
14
|
flatName: string;
|
|
15
15
|
subSets?: Array<TreeNode>;
|
|
16
16
|
hasData?: boolean;
|
|
17
|
+
hiAbsolute?: number;
|
|
17
18
|
hiCritical?: number;
|
|
18
19
|
hiNormal?: number;
|
|
19
20
|
lowAbsolute?: number;
|
|
@@ -75,6 +76,15 @@ export interface ObservationData {
|
|
|
75
76
|
obsDatetime: string;
|
|
76
77
|
value: string;
|
|
77
78
|
interpretation: OBSERVATION_INTERPRETATION;
|
|
79
|
+
// Reference range fields from observation-level (criteria-based)
|
|
80
|
+
// Note: Units are only at the concept/node level, not observation-level
|
|
81
|
+
hiAbsolute?: number;
|
|
82
|
+
hiCritical?: number;
|
|
83
|
+
hiNormal?: number;
|
|
84
|
+
lowAbsolute?: number;
|
|
85
|
+
lowCritical?: number;
|
|
86
|
+
lowNormal?: number;
|
|
87
|
+
range?: string; // Formatted range string for display
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
export interface ParsedTimeType {
|
|
@@ -124,6 +134,15 @@ export interface RowData extends TreeNode {
|
|
|
124
134
|
obsDatetime: string;
|
|
125
135
|
value: string;
|
|
126
136
|
interpretation: OBSERVATION_INTERPRETATION;
|
|
137
|
+
// Reference range fields from observation-level (criteria-based)
|
|
138
|
+
// Note: Units are only at the concept/node level, not observation-level
|
|
139
|
+
hiAbsolute?: number;
|
|
140
|
+
hiCritical?: number;
|
|
141
|
+
hiNormal?: number;
|
|
142
|
+
lowAbsolute?: number;
|
|
143
|
+
lowCritical?: number;
|
|
144
|
+
lowNormal?: number;
|
|
145
|
+
range?: string; // Formatted range string for display
|
|
127
146
|
}
|
|
128
147
|
| undefined
|
|
129
148
|
>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { getByTextWithMarkup } from 'tools';
|
|
4
5
|
import { showModal } from '@openmrs/esm-framework';
|
|
5
6
|
import { mockGroupedResults } from '__mocks__';
|
|
6
7
|
import { type FilterContextProps } from '../filter/filter-types';
|
|
@@ -70,6 +71,54 @@ describe('GroupedTimeline', () => {
|
|
|
70
71
|
expect(screen.getByText('2.9')).toBeInTheDocument();
|
|
71
72
|
});
|
|
72
73
|
|
|
74
|
+
it('displays most recent observation range when available', () => {
|
|
75
|
+
const contextWithObservationRanges = {
|
|
76
|
+
...mockFilterContext,
|
|
77
|
+
timelineData: {
|
|
78
|
+
...mockFilterContext.timelineData,
|
|
79
|
+
data: {
|
|
80
|
+
...mockFilterContext.timelineData.data,
|
|
81
|
+
rowData: [
|
|
82
|
+
{
|
|
83
|
+
...mockFilterContext.timelineData.data.rowData[0],
|
|
84
|
+
range: '0 – 50', // Node-level range
|
|
85
|
+
units: 'umol/L',
|
|
86
|
+
entries: [
|
|
87
|
+
{
|
|
88
|
+
obsDatetime: '2024-05-31 01:39:03.0',
|
|
89
|
+
value: '261.9',
|
|
90
|
+
interpretation: 'NORMAL',
|
|
91
|
+
lowNormal: 35,
|
|
92
|
+
hiNormal: 50,
|
|
93
|
+
range: '35 – 50', // Observation-level range (most recent)
|
|
94
|
+
// Note: Units are only at the concept/node level, not observation-level
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
obsDatetime: '2023-11-09 23:15:03.0',
|
|
98
|
+
value: '21.5',
|
|
99
|
+
interpretation: 'NORMAL',
|
|
100
|
+
lowNormal: 20,
|
|
101
|
+
hiNormal: 45,
|
|
102
|
+
range: '20 – 45', // Older observation-level range
|
|
103
|
+
// Note: Units are only at the concept/node level, not observation-level
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
renderGroupedTimeline(contextWithObservationRanges as FilterContextProps);
|
|
113
|
+
|
|
114
|
+
// Should display most recent observation's range (35 – 50) not node-level (0 – 50)
|
|
115
|
+
// Range and units are displayed separately in the same element
|
|
116
|
+
const rangeElement = getByTextWithMarkup(/35 – 50/);
|
|
117
|
+
expect(rangeElement).toBeInTheDocument();
|
|
118
|
+
// Verify that the same element also contains the units
|
|
119
|
+
expect(rangeElement).toHaveTextContent('35 – 50 umol/L');
|
|
120
|
+
});
|
|
121
|
+
|
|
73
122
|
it('correctly filters rows based on checkbox selection when someChecked is true', () => {
|
|
74
123
|
renderGroupedTimeline({
|
|
75
124
|
...mockFilterContext,
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
selectReferenceRange,
|
|
3
|
+
formatReferenceRange,
|
|
4
|
+
getMostRecentObservationWithRange,
|
|
5
|
+
rangeAlreadyHasUnits,
|
|
6
|
+
type ReferenceRanges,
|
|
7
|
+
} from './reference-range-helpers';
|
|
8
|
+
|
|
9
|
+
describe('Reference Range Helpers', () => {
|
|
10
|
+
describe('selectReferenceRange', () => {
|
|
11
|
+
it('returns null when both ranges are null', () => {
|
|
12
|
+
expect(selectReferenceRange(undefined, undefined)).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('returns node-level range when observation range is not available', () => {
|
|
16
|
+
const nodeRanges: ReferenceRanges = {
|
|
17
|
+
lowNormal: 0,
|
|
18
|
+
hiNormal: 50,
|
|
19
|
+
units: 'mg/dL',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
expect(selectReferenceRange(undefined, nodeRanges)).toEqual(nodeRanges);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns observation-level range when node range is not available', () => {
|
|
26
|
+
const observationRanges: ReferenceRanges = {
|
|
27
|
+
lowNormal: 35,
|
|
28
|
+
hiNormal: 147,
|
|
29
|
+
units: 'U/L',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
expect(selectReferenceRange(observationRanges, undefined)).toEqual(observationRanges);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('merges ranges with observation taking precedence', () => {
|
|
36
|
+
const observationRanges: ReferenceRanges = {
|
|
37
|
+
lowNormal: 35,
|
|
38
|
+
hiNormal: 147,
|
|
39
|
+
lowCritical: 25,
|
|
40
|
+
units: 'U/L',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const nodeRanges: ReferenceRanges = {
|
|
44
|
+
lowNormal: 0,
|
|
45
|
+
hiNormal: 270,
|
|
46
|
+
hiCritical: 541,
|
|
47
|
+
units: 'U/L',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const result = selectReferenceRange(observationRanges, nodeRanges);
|
|
51
|
+
|
|
52
|
+
expect(result).toEqual({
|
|
53
|
+
lowNormal: 35, // From observation
|
|
54
|
+
hiNormal: 147, // From observation
|
|
55
|
+
lowCritical: 25, // From observation
|
|
56
|
+
hiCritical: 541, // From node (observation doesn't have it)
|
|
57
|
+
units: 'U/L',
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('handles partial observation ranges', () => {
|
|
62
|
+
const observationRanges: ReferenceRanges = {
|
|
63
|
+
hiNormal: 147,
|
|
64
|
+
// Missing lowNormal
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const nodeRanges: ReferenceRanges = {
|
|
68
|
+
lowNormal: 0,
|
|
69
|
+
hiNormal: 270,
|
|
70
|
+
units: 'U/L',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const result = selectReferenceRange(observationRanges, nodeRanges);
|
|
74
|
+
|
|
75
|
+
expect(result).toEqual({
|
|
76
|
+
lowNormal: 0, // From node (observation doesn't have it)
|
|
77
|
+
hiNormal: 147, // From observation
|
|
78
|
+
units: 'U/L',
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('formatReferenceRange', () => {
|
|
84
|
+
it('returns "--" when ranges is null', () => {
|
|
85
|
+
expect(formatReferenceRange(null)).toBe('--');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('formats range with both lowNormal and hiNormal', () => {
|
|
89
|
+
const ranges: ReferenceRanges = {
|
|
90
|
+
lowNormal: 0,
|
|
91
|
+
hiNormal: 50,
|
|
92
|
+
units: 'mg/dL',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
expect(formatReferenceRange(ranges)).toBe('0 – 50 mg/dL');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('formats range without units', () => {
|
|
99
|
+
const ranges: ReferenceRanges = {
|
|
100
|
+
lowNormal: 0,
|
|
101
|
+
hiNormal: 50,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
expect(formatReferenceRange(ranges)).toBe('0 – 50');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns "--" when lowNormal or hiNormal is missing', () => {
|
|
108
|
+
const ranges1: ReferenceRanges = {
|
|
109
|
+
hiNormal: 50,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const ranges2: ReferenceRanges = {
|
|
113
|
+
lowNormal: 0,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
expect(formatReferenceRange(ranges1)).toBe('--');
|
|
117
|
+
expect(formatReferenceRange(ranges2)).toBe('--');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('uses provided units parameter when ranges.units is not available', () => {
|
|
121
|
+
const ranges: ReferenceRanges = {
|
|
122
|
+
lowNormal: 0,
|
|
123
|
+
hiNormal: 50,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
expect(formatReferenceRange(ranges, 'mg/dL')).toBe('0 – 50 mg/dL');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('getMostRecentObservationWithRange', () => {
|
|
131
|
+
it('returns null when observations array is empty', () => {
|
|
132
|
+
expect(getMostRecentObservationWithRange([])).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns null when no observations have range data', () => {
|
|
136
|
+
const observations = [
|
|
137
|
+
{ obsDatetime: '2024-01-01', value: '10' },
|
|
138
|
+
{ obsDatetime: '2024-01-02', value: '20' },
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
expect(getMostRecentObservationWithRange(observations)).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('returns the most recent observation with range data', () => {
|
|
145
|
+
const observations = [
|
|
146
|
+
{
|
|
147
|
+
obsDatetime: '2024-01-01',
|
|
148
|
+
value: '10',
|
|
149
|
+
lowNormal: 0,
|
|
150
|
+
hiNormal: 50,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
obsDatetime: '2024-01-03',
|
|
154
|
+
value: '30',
|
|
155
|
+
lowNormal: 35,
|
|
156
|
+
hiNormal: 147,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
obsDatetime: '2024-01-02',
|
|
160
|
+
value: '20',
|
|
161
|
+
// No range data
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const result = getMostRecentObservationWithRange(observations);
|
|
166
|
+
|
|
167
|
+
expect(result).toEqual({
|
|
168
|
+
obsDatetime: '2024-01-03',
|
|
169
|
+
value: '30',
|
|
170
|
+
lowNormal: 35,
|
|
171
|
+
hiNormal: 147,
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('handles observations with only lowNormal', () => {
|
|
176
|
+
const observations = [
|
|
177
|
+
{
|
|
178
|
+
obsDatetime: '2024-01-01',
|
|
179
|
+
value: '10',
|
|
180
|
+
lowNormal: 0,
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const result = getMostRecentObservationWithRange(observations);
|
|
185
|
+
|
|
186
|
+
expect(result).toEqual({
|
|
187
|
+
obsDatetime: '2024-01-01',
|
|
188
|
+
value: '10',
|
|
189
|
+
lowNormal: 0,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('handles observations with only hiNormal', () => {
|
|
194
|
+
const observations = [
|
|
195
|
+
{
|
|
196
|
+
obsDatetime: '2024-01-01',
|
|
197
|
+
value: '10',
|
|
198
|
+
hiNormal: 50,
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
const result = getMostRecentObservationWithRange(observations);
|
|
203
|
+
|
|
204
|
+
expect(result).toEqual({
|
|
205
|
+
obsDatetime: '2024-01-01',
|
|
206
|
+
value: '10',
|
|
207
|
+
hiNormal: 50,
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('filters out undefined entries', () => {
|
|
212
|
+
const observations = [
|
|
213
|
+
undefined,
|
|
214
|
+
{
|
|
215
|
+
obsDatetime: '2024-01-01',
|
|
216
|
+
value: '10',
|
|
217
|
+
lowNormal: 0,
|
|
218
|
+
hiNormal: 50,
|
|
219
|
+
},
|
|
220
|
+
undefined,
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
const result = getMostRecentObservationWithRange(observations);
|
|
224
|
+
|
|
225
|
+
expect(result).toEqual({
|
|
226
|
+
obsDatetime: '2024-01-01',
|
|
227
|
+
value: '10',
|
|
228
|
+
lowNormal: 0,
|
|
229
|
+
hiNormal: 50,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('rangeAlreadyHasUnits', () => {
|
|
235
|
+
it('returns false when range is undefined', () => {
|
|
236
|
+
expect(rangeAlreadyHasUnits(undefined, 'U/L')).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('returns false when units is undefined', () => {
|
|
240
|
+
expect(rangeAlreadyHasUnits('0 – 50', undefined)).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('returns false when both are undefined', () => {
|
|
244
|
+
expect(rangeAlreadyHasUnits(undefined, undefined)).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('returns true when range ends with units', () => {
|
|
248
|
+
expect(rangeAlreadyHasUnits('0 – 50 U/L', 'U/L')).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('returns true when range ends with units with space', () => {
|
|
252
|
+
expect(rangeAlreadyHasUnits('0 – 50 U/L', 'U/L')).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('returns false when range does not end with units', () => {
|
|
256
|
+
expect(rangeAlreadyHasUnits('0 – 50', 'U/L')).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('returns false when units appear in the middle of range', () => {
|
|
260
|
+
expect(rangeAlreadyHasUnits('5 mg/dL value', 'mg/dL')).toBe(false);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('handles trimmed strings correctly', () => {
|
|
264
|
+
expect(rangeAlreadyHasUnits(' 0 – 50 U/L ', ' U/L ')).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('returns false for empty strings', () => {
|
|
268
|
+
expect(rangeAlreadyHasUnits('', 'U/L')).toBe(false);
|
|
269
|
+
expect(rangeAlreadyHasUnits('0 – 50', '')).toBe(false);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { exist } from '../loadPatientTestData/helpers';
|
|
2
|
+
import type { OBSERVATION_INTERPRETATION } from '@openmrs/esm-patient-common-lib';
|
|
3
|
+
|
|
4
|
+
export interface ReferenceRanges {
|
|
5
|
+
hiAbsolute?: number;
|
|
6
|
+
hiCritical?: number;
|
|
7
|
+
hiNormal?: number;
|
|
8
|
+
lowAbsolute?: number;
|
|
9
|
+
lowCritical?: number;
|
|
10
|
+
lowNormal?: number;
|
|
11
|
+
units?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Merges observation-level and node-level reference ranges.
|
|
16
|
+
* Observation-level ranges take precedence when available.
|
|
17
|
+
*/
|
|
18
|
+
export function selectReferenceRange(
|
|
19
|
+
observationRanges?: ReferenceRanges,
|
|
20
|
+
nodeRanges?: ReferenceRanges,
|
|
21
|
+
): ReferenceRanges | null {
|
|
22
|
+
if (!observationRanges && !nodeRanges) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!observationRanges) {
|
|
27
|
+
return nodeRanges || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!nodeRanges) {
|
|
31
|
+
return observationRanges;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Merge: observation takes precedence for available fields.
|
|
35
|
+
// Note: Units are only at the concept/node level, so units will always come from nodeRanges.
|
|
36
|
+
return {
|
|
37
|
+
hiAbsolute: observationRanges.hiAbsolute ?? nodeRanges.hiAbsolute,
|
|
38
|
+
hiCritical: observationRanges.hiCritical ?? nodeRanges.hiCritical,
|
|
39
|
+
hiNormal: observationRanges.hiNormal ?? nodeRanges.hiNormal,
|
|
40
|
+
lowAbsolute: observationRanges.lowAbsolute ?? nodeRanges.lowAbsolute,
|
|
41
|
+
lowCritical: observationRanges.lowCritical ?? nodeRanges.lowCritical,
|
|
42
|
+
lowNormal: observationRanges.lowNormal ?? nodeRanges.lowNormal,
|
|
43
|
+
units: observationRanges.units ?? nodeRanges.units,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Formats reference range string using lowNormal and hiNormal.
|
|
49
|
+
* Note: Display format using lowAbsolute/hiAbsolute with >/< is handled in a separate ticket.
|
|
50
|
+
*/
|
|
51
|
+
export function formatReferenceRange(ranges: ReferenceRanges | null, units?: string): string {
|
|
52
|
+
if (!ranges) {
|
|
53
|
+
return '--';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { lowNormal, hiNormal } = ranges;
|
|
57
|
+
const displayUnits = ranges.units || units || '';
|
|
58
|
+
|
|
59
|
+
if (exist(lowNormal, hiNormal)) {
|
|
60
|
+
return `${lowNormal} – ${hiNormal}${displayUnits ? ` ${displayUnits}` : ''}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return '--';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks if a formatted range string already includes the units.
|
|
68
|
+
* This prevents duplicate units when appending units to a range that already has them.
|
|
69
|
+
* @param range The formatted range string (e.g., "0 – 50 U/L")
|
|
70
|
+
* @param units The units string (e.g., "U/L")
|
|
71
|
+
* @returns true if the range already ends with the units, false otherwise
|
|
72
|
+
*/
|
|
73
|
+
export function rangeAlreadyHasUnits(range: string | undefined, units: string | undefined): boolean {
|
|
74
|
+
if (!range || !units) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if range ends with units (with optional space before)
|
|
79
|
+
// This is more precise than includes() to avoid false positives
|
|
80
|
+
const trimmedRange = range.trim();
|
|
81
|
+
const trimmedUnits = units.trim();
|
|
82
|
+
return trimmedRange.endsWith(trimmedUnits) || trimmedRange.endsWith(` ${trimmedUnits}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Finds the most recent observation that has reference range data.
|
|
87
|
+
*/
|
|
88
|
+
export function getMostRecentObservationWithRange<
|
|
89
|
+
T extends { obsDatetime: string; lowNormal?: number; hiNormal?: number },
|
|
90
|
+
>(observations: Array<T | undefined>): T | null {
|
|
91
|
+
if (!observations || observations.length === 0) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Filter out undefined and find observations with range data
|
|
96
|
+
const validObservations = observations.filter(
|
|
97
|
+
(obs): obs is T => obs !== undefined && (obs.lowNormal !== undefined || obs.hiNormal !== undefined),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (validObservations.length === 0) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Sort by obsDatetime descending (most recent first)
|
|
105
|
+
const sorted = [...validObservations].sort((a, b) => {
|
|
106
|
+
const dateA = new Date(a.obsDatetime).getTime();
|
|
107
|
+
const dateB = new Date(b.obsDatetime).getTime();
|
|
108
|
+
return dateB - dateA;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return sorted[0];
|
|
112
|
+
}
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
TimelineDataGroupProps,
|
|
11
11
|
} from './grouped-timeline-types';
|
|
12
12
|
import FilterContext from '../filter/filter-context';
|
|
13
|
+
import { getMostRecentObservationWithRange } from './reference-range-helpers';
|
|
13
14
|
import styles from './grouped-timeline.scss';
|
|
14
15
|
|
|
15
16
|
export const ShadowBox: React.FC = () => <div className={styles['shadow-box']} />;
|
|
@@ -196,14 +197,20 @@ const DataRows: React.FC<DataRowsProps> = ({ patientUuid, timeColumns, rowData,
|
|
|
196
197
|
<Grid dataColumns={timeColumns.length} padding style={{ gridColumn: 'span 2' }}>
|
|
197
198
|
{rowData.map((row, index) => {
|
|
198
199
|
const obs = row.entries;
|
|
199
|
-
const {
|
|
200
|
+
const { obs: values } = row;
|
|
200
201
|
const isString = isNaN(parseFloat(values?.[0]?.value));
|
|
202
|
+
|
|
203
|
+
// Note: Units are only at the concept/node level, not observation-level
|
|
204
|
+
const mostRecentObsWithRange = getMostRecentObservationWithRange(row.entries);
|
|
205
|
+
const displayRange = mostRecentObsWithRange?.range ?? row.range ?? '';
|
|
206
|
+
const displayUnits = row.units ?? '';
|
|
207
|
+
|
|
201
208
|
return (
|
|
202
209
|
<React.Fragment key={index}>
|
|
203
210
|
<NewRowStartCell
|
|
204
211
|
{...{
|
|
205
|
-
units,
|
|
206
|
-
range,
|
|
212
|
+
units: displayUnits,
|
|
213
|
+
range: displayRange,
|
|
207
214
|
title: row.display,
|
|
208
215
|
shadow: showShadow,
|
|
209
216
|
conceptUuid: row.conceptUuid,
|