@clinicemr/esm-post-registration-redirect-app 1.1.1 → 1.2.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/802.js CHANGED
@@ -1 +1 @@
1
- "use strict";(globalThis.webpackChunk_clinicemr_esm_post_registration_redirect_app=globalThis.webpackChunk_clinicemr_esm_post_registration_redirect_app||[]).push([[802],{7237(e,t,n){n.d(t,{A:()=>a});var r=n(935),i=n.n(r)()((function(e){return e[1]}));i.push([e.id,".-esm-post-registration-redirect__my-registered-patients__container___qhgFl{padding:1.5rem 2rem;max-width:1200px;margin:0 auto}.-esm-post-registration-redirect__my-registered-patients__title___qy2WX{margin-bottom:1rem;font-weight:600}.-esm-post-registration-redirect__my-registered-patients__emptyTile___yyuEf{padding:2rem;text-align:center}",""]),i.locals={container:"-esm-post-registration-redirect__my-registered-patients__container___qhgFl",title:"-esm-post-registration-redirect__my-registered-patients__title___qy2WX",emptyTile:"-esm-post-registration-redirect__my-registered-patients__emptyTile___yyuEf"};const a=i},5802(e,t,n){n.r(t),n.d(t,{default:()=>S});var r=n(1343),i=n(2339),a=n(1123),o=n(5803),l=n(5848),s=n(5940),c=n(2591),d=n.n(c),u=n(1740),m=n.n(u),p=n(8128),g=n.n(p),y=n(855),f=n.n(y),_=n(3051),v=n.n(_),h=n(3656),b=n.n(h),O=n(7237),E={};E.styleTagTransform=b(),E.setAttributes=f(),E.insert=g().bind(null,"head"),E.domAPI=m(),E.insertStyleElement=v(),d()(O.A,E);const w=O.A&&O.A.locals?O.A.locals:void 0;function k(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function P(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{},r=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(r=r.concat(Object.getOwnPropertySymbols(n).filter((function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable})))),r.forEach((function(t){k(e,t,n[t])}))}return e}function j(e,t){return t=null!=t?t:{},Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):function(e){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t.push.apply(t,n)}return t}(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))})),e}const S=function(){var e,t,n,c=(0,i.useTranslation)().t,d=(0,s.useSession)(),u=(0,s.useConfig)(),m=null==d||null===(e=d.user)||void 0===e?void 0:e.uuid,p=m&&u.registrationEncounterTypeUuid?"".concat(s.restBaseUrl,"/encounter?encounterType=").concat(u.registrationEncounterTypeUuid)+"&fromdate=".concat(((t=new Date).setDate(t.getDate()-365),t.toISOString().split("T")[0]))+"&v=full&limit=".concat(Math.max(50,4*u.pageSize)):null,g=(0,a.Ay)(p,s.openmrsFetch),y=g.data,f=g.error,_=g.isLoading,v=(0,r.useMemo)((function(){var e,t,n=(null!==(e=null==y||null===(t=y.data)||void 0===t?void 0:t.results)&&void 0!==e?e:[]).filter((function(e){var t,n;return(null===(n=e.auditInfo)||void 0===n||null===(t=n.creator)||void 0===t?void 0:t.uuid)===m})),r=new Map,i=!0,a=!1,o=void 0;try{for(var l,c=n[Symbol.iterator]();!(i=(l=c.next()).done);i=!0){var d,p,g=l.value,f=r.get(g.patient.uuid);(!f||(null!==(d=g.encounterDatetime)&&void 0!==d?d:"")>(null!==(p=f.encounterDatetime)&&void 0!==p?p:""))&&r.set(g.patient.uuid,g)}}catch(e){a=!0,o=e}finally{try{i||null==c.return||c.return()}finally{if(a)throw o}}return Array.from(r.values()).sort((function(e,t){var n,r;return(null!==(n=t.encounterDatetime)&&void 0!==n?n:"").localeCompare(null!==(r=e.encounterDatetime)&&void 0!==r?r:"")})).slice(0,u.pageSize).map((function(e){var t,n,r,i,a,o,l,c=null!==(t=null===(i=e.patient.identifiers)||void 0===i?void 0:i.find((function(e){return e.preferred})))&&void 0!==t?t:null===(a=e.patient.identifiers)||void 0===a?void 0:a[0];return{id:e.patient.uuid,name:e.patient.display,identifier:null!==(n=null==c?void 0:c.identifier)&&void 0!==n?n:"—",gender:null!==(r=null===(o=e.patient.person)||void 0===o?void 0:o.gender)&&void 0!==r?r:"—",age:null!=(null===(l=e.patient.person)||void 0===l?void 0:l.age)?String(e.patient.person.age):"—",registeredOn:e.encounterDatetime?(0,s.formatDate)(new Date(e.encounterDatetime)):"—"}}))}),[y,m,u.pageSize]),h=[{key:"name",header:c("patientName","Name")},{key:"identifier",header:c("identifier","Identifier")},{key:"gender",header:c("gender","Gender")},{key:"age",header:c("age","Age")},{key:"registeredOn",header:c("registeredOn","Registered on")},{key:"actions",header:""}];return _?r.createElement("div",{className:w.container},r.createElement("h2",{className:w.title},c("myRegisteredPatients","My Registered Patients")),r.createElement(o.OOb,{headers:h,rowCount:5})):f?r.createElement("div",{className:w.container},r.createElement(o.jeF,{kind:"error",title:c("errorLoading","Could not load patients you've registered."),subtitle:String(null!==(n=null==f?void 0:f.message)&&void 0!==n?n:""),lowContrast:!0,hideCloseButton:!0})):r.createElement("div",{className:w.container},r.createElement("h2",{className:w.title},c("myRegisteredPatients","My Registered Patients")),0===v.length?r.createElement(o.FAs,{className:w.emptyTile},r.createElement("p",null,c("noPatientsRegistered","You haven't registered any patients yet."))):r.createElement(o.bQt,{rows:v,headers:h,useZebraStyles:!0},(function(e){var t=e.rows,n=e.headers,i=e.getHeaderProps,a=e.getRowProps,d=e.getTableProps;return r.createElement(o.K3K,null,r.createElement(o.XIK,d(),r.createElement(o.ndF,null,r.createElement(o.Hjg,null,n.map((function(e){return r.createElement(o.A0N,j(P({},i({header:e})),{key:e.key}),e.header)})))),r.createElement(o.BFY,null,t.map((function(e){return r.createElement(o.Hjg,j(P({},a({row:e})),{key:e.id}),e.cells.map((function(t){return"actions"===t.info.header?r.createElement(o.nA6,{key:t.id},r.createElement(o.$nd,{kind:"ghost",size:"sm",renderIcon:l.Qp,onClick:function(){return(0,s.navigate)({to:"${openmrsSpaBase}/patient/".concat(e.id,"/chart")})}},c("openChart","Open chart"))):r.createElement(o.nA6,{key:t.id},t.value)})))})))))})))}}}]);
1
+ "use strict";(globalThis.webpackChunk_clinicemr_esm_post_registration_redirect_app=globalThis.webpackChunk_clinicemr_esm_post_registration_redirect_app||[]).push([[802],{7237(e,t,n){n.d(t,{A:()=>a});var r=n(935),i=n.n(r)()((function(e){return e[1]}));i.push([e.id,".-esm-post-registration-redirect__my-registered-patients__container___qhgFl{padding:1.5rem 2rem;max-width:1200px;margin:0 auto}.-esm-post-registration-redirect__my-registered-patients__title___qy2WX{margin-bottom:1rem;font-weight:600}.-esm-post-registration-redirect__my-registered-patients__emptyTile___yyuEf{padding:2rem;text-align:center}",""]),i.locals={container:"-esm-post-registration-redirect__my-registered-patients__container___qhgFl",title:"-esm-post-registration-redirect__my-registered-patients__title___qy2WX",emptyTile:"-esm-post-registration-redirect__my-registered-patients__emptyTile___yyuEf"};const a=i},5802(e,t,n){n.r(t),n.d(t,{default:()=>R});var r=n(1343),i=n(2339),a=n(1123),o=n(5803),l=n(5848),s=n(5940),c=n(8767),u=n(2591),d=n.n(u),p=n(1740),f=n.n(p),m=n(8128),g=n.n(m),y=n(855),v=n.n(y),h=n(3051),b=n.n(h),_=n(3656),w=n.n(_),O=n(7237),E={};E.styleTagTransform=w(),E.setAttributes=v(),E.insert=g().bind(null,"head"),E.domAPI=f(),E.insertStyleElement=b(),d()(O.A,E);const P=O.A&&O.A.locals?O.A.locals:void 0;function k(e,t,n,r,i,a,o){try{var l=e[a](o),s=l.value}catch(e){return void n(e)}l.done?t(s):Promise.resolve(s).then(r,i)}function S(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function j(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{},r=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(r=r.concat(Object.getOwnPropertySymbols(n).filter((function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable})))),r.forEach((function(t){S(e,t,n[t])}))}return e}function A(e,t){return t=null!=t?t:{},Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):function(e){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t.push.apply(t,n)}return t}(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))})),e}function N(e){return(t=function(){return function(e,t){var n,r,i,a={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]},o=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),l=Object.defineProperty;return l(o,"next",{value:s(0)}),l(o,"throw",{value:s(1)}),l(o,"return",{value:s(2)}),"function"==typeof Symbol&&l(o,Symbol.iterator,{value:function(){return this}}),o;function s(l){return function(s){return function(l){if(n)throw new TypeError("Generator is already executing.");for(;o&&(o=0,l[0]&&(a=0)),a;)try{if(n=1,r&&(i=2&l[0]?r.return:l[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,l[1])).done)return i;switch(r=0,i&&(l=[2&l[0],i.value]),l[0]){case 0:case 1:i=l;break;case 4:return a.label++,{value:l[1],done:!1};case 5:a.label++,r=l[1],l=[0];continue;case 7:l=a.ops.pop(),a.trys.pop();continue;default:if(!((i=(i=a.trys).length>0&&i[i.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!i||l[1]>i[0]&&l[1]<i[3])){a.label=l[1];break}if(6===l[0]&&a.label<i[1]){a.label=i[1],i=l;break}if(i&&a.label<i[2]){a.label=i[2],a.ops.push(l);break}i[2]&&a.ops.pop(),a.trys.pop();continue}l=t.call(e,a)}catch(e){l=[6,e],r=0}finally{n=i=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}([l,s])}}}(this,(function(t){switch(t.label){case 0:return 0===e.length?[2,[]]:[4,Promise.allSettled(e.map((function(e){return(0,s.openmrsFetch)("".concat(s.restBaseUrl,"/patient/").concat(e,"?v=").concat(encodeURIComponent("custom:(uuid,display,identifiers:(identifier,preferred),person:(gender,age))"))).then((function(e){return e.data}))})))];case 1:return[2,t.sent().filter((function(e){return"fulfilled"===e.status})).map((function(e){return e.value}))]}}))},function(){var e=this,n=arguments;return new Promise((function(r,i){var a=t.apply(e,n);function o(e){k(a,r,i,o,l,"next",e)}function l(e){k(a,r,i,o,l,"throw",e)}o(void 0)}))})();var t}const R=function(){var e,t,n=(0,i.useTranslation)().t,u=(0,s.useSession)(),d=(0,s.useConfig)(),p=null==u||null===(e=u.user)||void 0===e?void 0:e.uuid,f=(0,r.useMemo)((function(){return p?(0,c.f)(p).slice(0,d.pageSize):[]}),[p,d.pageSize]),m=p&&f.length>0?["my-registered-patients",p,f.map((function(e){return e.patientUuid})).join(",")]:null,g=(0,a.Ay)(m,(function(){return N(f.map((function(e){return e.patientUuid})))})),y=g.data,v=g.error,h=g.isLoading,b=(0,r.useMemo)((function(){var e=new Map((null!=y?y:[]).map((function(e){return[e.uuid,e]})));return f.map((function(t){var n,r,i,a,o,l,c,u=e.get(t.patientUuid);if(!u)return null;var d=null!==(n=null===(a=u.identifiers)||void 0===a?void 0:a.find((function(e){return e.preferred})))&&void 0!==n?n:null===(o=u.identifiers)||void 0===o?void 0:o[0];return{id:u.uuid,name:u.display,identifier:null!==(r=null==d?void 0:d.identifier)&&void 0!==r?r:"—",gender:null!==(i=null===(l=u.person)||void 0===l?void 0:l.gender)&&void 0!==i?i:"—",age:null!=(null===(c=u.person)||void 0===c?void 0:c.age)?String(u.person.age):"—",registeredOn:t.registeredAt?(0,s.formatDate)(new Date(t.registeredAt)):"—"}})).filter(Boolean)}),[y,f]),_=[{key:"name",header:n("patientName","Name")},{key:"identifier",header:n("identifier","Identifier")},{key:"gender",header:n("gender","Gender")},{key:"age",header:n("age","Age")},{key:"registeredOn",header:n("registeredOn","Registered on")},{key:"actions",header:""}];return 0===f.length?r.createElement("div",{className:P.container},r.createElement("h2",{className:P.title},n("myRegisteredPatients","My Registered Patients")),r.createElement(o.FAs,{className:P.emptyTile},r.createElement("p",null,n("noPatientsRegistered","You haven't registered any patients yet.")))):h?r.createElement("div",{className:P.container},r.createElement("h2",{className:P.title},n("myRegisteredPatients","My Registered Patients")),r.createElement(o.OOb,{headers:_,rowCount:Math.min(f.length,5)})):v?r.createElement("div",{className:P.container},r.createElement("h2",{className:P.title},n("myRegisteredPatients","My Registered Patients")),r.createElement(o.jeF,{kind:"error",title:n("errorLoading","Could not load patients you've registered."),subtitle:String(null!==(t=null==v?void 0:v.message)&&void 0!==t?t:""),lowContrast:!0,hideCloseButton:!0})):r.createElement("div",{className:P.container},r.createElement("h2",{className:P.title},n("myRegisteredPatients","My Registered Patients")),r.createElement(o.bQt,{rows:b,headers:_,useZebraStyles:!0},(function(e){var t=e.rows,i=e.headers,a=e.getHeaderProps,c=e.getRowProps,u=e.getTableProps;return r.createElement(o.K3K,null,r.createElement(o.XIK,u(),r.createElement(o.ndF,null,r.createElement(o.Hjg,null,i.map((function(e){return r.createElement(o.A0N,A(j({},a({header:e})),{key:e.key}),e.header)})))),r.createElement(o.BFY,null,t.map((function(e){return r.createElement(o.Hjg,A(j({},c({row:e})),{key:e.id}),e.cells.map((function(t){return"actions"===t.info.header?r.createElement(o.nA6,{key:t.id},r.createElement(o.$nd,{kind:"ghost",size:"sm",renderIcon:l.Qp,onClick:function(){return(0,s.navigate)({to:"${openmrsSpaBase}/patient/".concat(e.id,"/chart")})}},n("openChart","Open chart"))):r.createElement(o.nA6,{key:t.id},t.value)})))})))))})))}},8767(e,t,n){n.d(t,{D:()=>a,f:()=>o});var r="clinicemr.registeredPatients";function i(){try{var e=window.localStorage.getItem(r);if(!e)return{};var t=JSON.parse(e);return t&&"object"==(void 0===t?"undefined":(n=t)&&"undefined"!=typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)?t:{}}catch(e){return{}}var n}function a(e,t){var n;if(e&&t){var a=i(),o=(null!==(n=a[e])&&void 0!==n?n:[]).filter((function(e){return e.patientUuid!==t}));o.unshift({patientUuid:t,registeredAt:(new Date).toISOString()}),a[e]=o.slice(0,200),function(e){try{window.localStorage.setItem(r,JSON.stringify(e))}catch(e){}}(a)}}function o(e){var t;return e&&null!==(t=i()[e])&&void 0!==t?t:[]}}}]);
package/dist/918.js CHANGED
@@ -1 +1 @@
1
- "use strict";(globalThis.webpackChunk_clinicemr_esm_post_registration_redirect_app=globalThis.webpackChunk_clinicemr_esm_post_registration_redirect_app||[]).push([[918],{2918(e,t,i){i.r(t),i.d(t,{default:()=>s});var n=i(1343),r=i(2339),a=i(5803),o=i(5940);const s=function(){var e=(0,r.useTranslation)().t,t=(0,o.useSession)(),i=(0,o.useConfig)();return(0,n.useEffect)((function(){var e;if(null==t?void 0:t.user){var n,r,a,s=-1===(a=(r=window.location.pathname.replace(/\/$/,"").split("/")).findIndex((function(e){return"post-registration"===e})))||a+1>=r.length?"":null!==(n=r[a+1])&&void 0!==n?n:"";(null!==(e=t.user.roles)&&void 0!==e?e:[]).map((function(e){var t,i;return String(null!==(t=null!==(i=e.display)&&void 0!==i?i:e.name)&&void 0!==t?t:"")})).some((function(e){return e.trim().toLowerCase()===i.registrationClerkRoleName.trim().toLowerCase()}))?(0,o.navigate)({to:"${openmrsSpaBase}/".concat(i.registeredPatientsListPath)}):s?(0,o.navigate)({to:"${openmrsSpaBase}/patient/".concat(s,"/chart")}):(0,o.navigate)({to:"${openmrsSpaBase}/home"})}}),[t,i.registrationClerkRoleName,i.registeredPatientsListPath]),n.createElement("div",{style:{padding:"2rem"}},n.createElement(a.OuH,{description:e("redirecting","Redirecting…")}))}}}]);
1
+ "use strict";(globalThis.webpackChunk_clinicemr_esm_post_registration_redirect_app=globalThis.webpackChunk_clinicemr_esm_post_registration_redirect_app||[]).push([[918],{2918(e,t,r){r.r(t),r.d(t,{default:()=>u});var i=r(1343),n=r(2339),a=r(5803),o=r(5940),s=r(8767);const u=function(){var e=(0,n.useTranslation)().t,t=(0,o.useSession)(),r=(0,o.useConfig)();return(0,i.useEffect)((function(){var e;if(null==t?void 0:t.user){var i,n,a,u=-1===(a=(n=window.location.pathname.replace(/\/$/,"").split("/")).findIndex((function(e){return"post-registration"===e})))||a+1>=n.length?"":null!==(i=n[a+1])&&void 0!==i?i:"",c=(null!==(e=t.user.roles)&&void 0!==e?e:[]).map((function(e){var t,r;return String(null!==(t=null!==(r=e.display)&&void 0!==r?r:e.name)&&void 0!==t?t:"")})).some((function(e){return e.trim().toLowerCase()===r.registrationClerkRoleName.trim().toLowerCase()}));u&&t.user.uuid&&(0,s.D)(t.user.uuid,u),c?(0,o.navigate)({to:"${openmrsSpaBase}/".concat(r.registeredPatientsListPath)}):u?(0,o.navigate)({to:"${openmrsSpaBase}/patient/".concat(u,"/chart")}):(0,o.navigate)({to:"${openmrsSpaBase}/home"})}}),[t,r.registrationClerkRoleName,r.registeredPatientsListPath]),i.createElement("div",{style:{padding:"2rem"}},i.createElement(a.OuH,{description:e("redirecting","Redirecting…")}))}},8767(e,t,r){r.d(t,{D:()=>a,f:()=>o});var i="clinicemr.registeredPatients";function n(){try{var e=window.localStorage.getItem(i);if(!e)return{};var t=JSON.parse(e);return t&&"object"==(void 0===t?"undefined":(r=t)&&"undefined"!=typeof Symbol&&r.constructor===Symbol?"symbol":typeof r)?t:{}}catch(e){return{}}var r}function a(e,t){var r;if(e&&t){var a=n(),o=(null!==(r=a[e])&&void 0!==r?r:[]).filter((function(e){return e.patientUuid!==t}));o.unshift({patientUuid:t,registeredAt:(new Date).toISOString()}),a[e]=o.slice(0,200),function(e){try{window.localStorage.setItem(i,JSON.stringify(e))}catch(e){}}(a)}}function o(e){var t;return e&&null!==(t=n()[e])&&void 0!==t?t:[]}}}]);
@@ -328,9 +328,9 @@
328
328
  "initial": false,
329
329
  "entry": false,
330
330
  "recorded": false,
331
- "size": 13626,
331
+ "size": 19326,
332
332
  "sizes": {
333
- "javascript": 13626
333
+ "javascript": 19326
334
334
  },
335
335
  "names": [],
336
336
  "idHints": [],
@@ -342,7 +342,7 @@
342
342
  "802.js"
343
343
  ],
344
344
  "auxiliaryFiles": [],
345
- "hash": "19b70b63b180ffb9",
345
+ "hash": "8f90e2f442b22ddf",
346
346
  "childrenByOrder": {}
347
347
  },
348
348
  {
@@ -400,9 +400,9 @@
400
400
  "initial": false,
401
401
  "entry": false,
402
402
  "recorded": false,
403
- "size": 3077,
403
+ "size": 5679,
404
404
  "sizes": {
405
- "javascript": 3077
405
+ "javascript": 5679
406
406
  },
407
407
  "names": [],
408
408
  "idHints": [],
@@ -414,7 +414,7 @@
414
414
  "918.js"
415
415
  ],
416
416
  "auxiliaryFiles": [],
417
- "hash": "a2d38887225f4d7b",
417
+ "hash": "442cd1b78639a318",
418
418
  "childrenByOrder": {}
419
419
  }
420
420
  ]
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.2.0"},"pages":[{"component":"postRegistrationRedirect","route":"post-registration"},{"component":"myRegisteredPatients","route":"my-registered-patients"}],"extensions":[{"name":"registration-clerk-lockdown","component":"registrationClerkLockdown","slot":"top-nav-actions-slot"}],"version":"1.1.1"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.2.0"},"pages":[{"component":"postRegistrationRedirect","route":"post-registration"},{"component":"myRegisteredPatients","route":"my-registered-patients"}],"extensions":[{"name":"registration-clerk-lockdown","component":"registrationClerkLockdown","slot":"top-nav-actions-slot"}],"version":"1.2.0"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clinicemr/esm-post-registration-redirect-app",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Role-aware post-registration redirect for OpenMRS SSUUBO distro",
6
6
  "browser": "dist/openmrs-esm-post-registration-redirect-app.js",
@@ -25,36 +25,37 @@ import {
25
25
  formatDate,
26
26
  } from '@openmrs/esm-framework';
27
27
  import type { PostRegistrationRedirectConfig } from './config-schema';
28
+ import { getRegisteredPatients } from './registered-patients-store';
28
29
  import styles from './my-registered-patients.scss';
29
30
 
30
- interface RegistrationEncounter {
31
+ interface PatientResource {
31
32
  uuid: string;
32
- encounterDatetime: string;
33
- patient: {
34
- uuid: string;
35
- display: string;
36
- identifiers?: Array<{ identifier: string; preferred?: boolean }>;
37
- person?: { gender?: string; age?: number };
38
- };
39
- auditInfo?: {
40
- creator?: { uuid: string; display?: string };
41
- };
33
+ display: string;
34
+ identifiers?: Array<{ identifier: string; preferred?: boolean }>;
35
+ person?: { gender?: string; age?: number };
42
36
  }
43
37
 
44
- interface EncounterListResponse {
45
- results: RegistrationEncounter[];
46
- }
47
-
48
- // Use the built-in `full` representation. Some OpenMRS REST builds return
49
- // 500 when asked to resolve deeply-nested custom reps (particularly the
50
- // `auditInfo:(creator:(...))` shape) on the encounter search endpoint.
51
- // `full` always works and gives us everything we need; we filter client-side.
52
- const FROM_DATE_DAYS_BACK = 365;
38
+ const patientRep =
39
+ 'custom:(uuid,display,identifiers:(identifier,preferred),person:(gender,age))';
53
40
 
54
- function isoFromDate(daysBack: number): string {
55
- const d = new Date();
56
- d.setDate(d.getDate() - daysBack);
57
- return d.toISOString().split('T')[0];
41
+ /**
42
+ * Fetch a list of patients by UUID via the OpenMRS REST patient endpoint.
43
+ * The single-patient endpoint (`/ws/rest/v1/patient/{uuid}`) is reliable on
44
+ * every OpenMRS REST build, unlike the encounter search endpoint which
45
+ * requires `patient` as a parameter and 500s otherwise.
46
+ */
47
+ async function fetchPatients(uuids: string[]): Promise<PatientResource[]> {
48
+ if (uuids.length === 0) return [];
49
+ const results = await Promise.allSettled(
50
+ uuids.map((uuid) =>
51
+ openmrsFetch<PatientResource>(
52
+ `${restBaseUrl}/patient/${uuid}?v=${encodeURIComponent(patientRep)}`,
53
+ ).then((res) => res.data),
54
+ ),
55
+ );
56
+ return results
57
+ .filter((r): r is PromiseFulfilledResult<PatientResource> => r.status === 'fulfilled')
58
+ .map((r) => r.value);
58
59
  }
59
60
 
60
61
  const MyRegisteredPatients: React.FC = () => {
@@ -63,41 +64,44 @@ const MyRegisteredPatients: React.FC = () => {
63
64
  const config = useConfig<PostRegistrationRedirectConfig>();
64
65
  const currentUserUuid = session?.user?.uuid;
65
66
 
66
- const url =
67
- currentUserUuid && config.registrationEncounterTypeUuid
68
- ? `${restBaseUrl}/encounter?encounterType=${config.registrationEncounterTypeUuid}` +
69
- `&fromdate=${isoFromDate(FROM_DATE_DAYS_BACK)}` +
70
- `&v=full&limit=${Math.max(50, config.pageSize * 4)}`
71
- : null;
67
+ // Read the per-user list of registered patient UUIDs (most recent first).
68
+ const entries = useMemo(
69
+ () => (currentUserUuid ? getRegisteredPatients(currentUserUuid).slice(0, config.pageSize) : []),
70
+ [currentUserUuid, config.pageSize],
71
+ );
72
+
73
+ const swrKey = currentUserUuid && entries.length > 0
74
+ ? ['my-registered-patients', currentUserUuid, entries.map((e) => e.patientUuid).join(',')]
75
+ : null;
72
76
 
73
- const { data, error, isLoading } = useSWR<{ data: EncounterListResponse }>(url, openmrsFetch);
77
+ const { data, error, isLoading } = useSWR(swrKey, () => fetchPatients(entries.map((e) => e.patientUuid)));
74
78
 
75
79
  const rows = useMemo(() => {
76
- const encounters = data?.data?.results ?? [];
77
- const mine = encounters.filter((e) => e.auditInfo?.creator?.uuid === currentUserUuid);
78
- // Dedupe by patient uuid, keeping the most recent encounter date
79
- const byPatient = new Map<string, RegistrationEncounter>();
80
- for (const e of mine) {
81
- const existing = byPatient.get(e.patient.uuid);
82
- if (!existing || (e.encounterDatetime ?? '') > (existing.encounterDatetime ?? '')) {
83
- byPatient.set(e.patient.uuid, e);
84
- }
85
- }
86
- const list = Array.from(byPatient.values()).sort((a, b) =>
87
- (b.encounterDatetime ?? '').localeCompare(a.encounterDatetime ?? ''),
88
- );
89
- return list.slice(0, config.pageSize).map((e) => {
90
- const preferredId = e.patient.identifiers?.find((i) => i.preferred) ?? e.patient.identifiers?.[0];
91
- return {
92
- id: e.patient.uuid,
93
- name: e.patient.display,
94
- identifier: preferredId?.identifier ?? '—',
95
- gender: e.patient.person?.gender ?? '—',
96
- age: e.patient.person?.age != null ? String(e.patient.person.age) : '—',
97
- registeredOn: e.encounterDatetime ? formatDate(new Date(e.encounterDatetime)) : '—',
98
- };
99
- });
100
- }, [data, currentUserUuid, config.pageSize]);
80
+ const patients = data ?? [];
81
+ const byUuid = new Map(patients.map((p) => [p.uuid, p]));
82
+ return entries
83
+ .map((entry) => {
84
+ const p = byUuid.get(entry.patientUuid);
85
+ if (!p) return null;
86
+ const preferredId = p.identifiers?.find((i) => i.preferred) ?? p.identifiers?.[0];
87
+ return {
88
+ id: p.uuid,
89
+ name: p.display,
90
+ identifier: preferredId?.identifier ?? '—',
91
+ gender: p.person?.gender ?? '',
92
+ age: p.person?.age != null ? String(p.person.age) : '—',
93
+ registeredOn: entry.registeredAt ? formatDate(new Date(entry.registeredAt)) : '—',
94
+ };
95
+ })
96
+ .filter(Boolean) as Array<{
97
+ id: string;
98
+ name: string;
99
+ identifier: string;
100
+ gender: string;
101
+ age: string;
102
+ registeredOn: string;
103
+ }>;
104
+ }, [data, entries]);
101
105
 
102
106
  const headers = [
103
107
  { key: 'name', header: t('patientName', 'Name') },
@@ -108,11 +112,22 @@ const MyRegisteredPatients: React.FC = () => {
108
112
  { key: 'actions', header: '' },
109
113
  ];
110
114
 
115
+ if (entries.length === 0) {
116
+ return (
117
+ <div className={styles.container}>
118
+ <h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
119
+ <Tile className={styles.emptyTile}>
120
+ <p>{t('noPatientsRegistered', "You haven't registered any patients yet.")}</p>
121
+ </Tile>
122
+ </div>
123
+ );
124
+ }
125
+
111
126
  if (isLoading) {
112
127
  return (
113
128
  <div className={styles.container}>
114
129
  <h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
115
- <DataTableSkeleton headers={headers} rowCount={5} />
130
+ <DataTableSkeleton headers={headers} rowCount={Math.min(entries.length, 5)} />
116
131
  </div>
117
132
  );
118
133
  }
@@ -120,6 +135,7 @@ const MyRegisteredPatients: React.FC = () => {
120
135
  if (error) {
121
136
  return (
122
137
  <div className={styles.container}>
138
+ <h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
123
139
  <InlineNotification
124
140
  kind="error"
125
141
  title={t('errorLoading', "Could not load patients you've registered.")}
@@ -134,53 +150,47 @@ const MyRegisteredPatients: React.FC = () => {
134
150
  return (
135
151
  <div className={styles.container}>
136
152
  <h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
137
- {rows.length === 0 ? (
138
- <Tile className={styles.emptyTile}>
139
- <p>{t('noPatientsRegistered', "You haven't registered any patients yet.")}</p>
140
- </Tile>
141
- ) : (
142
- <DataTable rows={rows} headers={headers} useZebraStyles>
143
- {({ rows: dataRows, headers: dataHeaders, getHeaderProps, getRowProps, getTableProps }) => (
144
- <TableContainer>
145
- <Table {...getTableProps()}>
146
- <TableHead>
147
- <TableRow>
148
- {dataHeaders.map((h) => (
149
- <TableHeader {...getHeaderProps({ header: h })} key={h.key}>
150
- {h.header}
151
- </TableHeader>
152
- ))}
153
- </TableRow>
154
- </TableHead>
155
- <TableBody>
156
- {dataRows.map((r) => (
157
- <TableRow {...getRowProps({ row: r })} key={r.id}>
158
- {r.cells.map((cell) =>
159
- cell.info.header === 'actions' ? (
160
- <TableCell key={cell.id}>
161
- <Button
162
- kind="ghost"
163
- size="sm"
164
- renderIcon={ArrowRight}
165
- onClick={() =>
166
- navigate({ to: `\${openmrsSpaBase}/patient/${r.id}/chart` })
167
- }
168
- >
169
- {t('openChart', 'Open chart')}
170
- </Button>
171
- </TableCell>
172
- ) : (
173
- <TableCell key={cell.id}>{cell.value}</TableCell>
174
- ),
175
- )}
176
- </TableRow>
153
+ <DataTable rows={rows} headers={headers} useZebraStyles>
154
+ {({ rows: dataRows, headers: dataHeaders, getHeaderProps, getRowProps, getTableProps }) => (
155
+ <TableContainer>
156
+ <Table {...getTableProps()}>
157
+ <TableHead>
158
+ <TableRow>
159
+ {dataHeaders.map((h) => (
160
+ <TableHeader {...getHeaderProps({ header: h })} key={h.key}>
161
+ {h.header}
162
+ </TableHeader>
177
163
  ))}
178
- </TableBody>
179
- </Table>
180
- </TableContainer>
181
- )}
182
- </DataTable>
183
- )}
164
+ </TableRow>
165
+ </TableHead>
166
+ <TableBody>
167
+ {dataRows.map((r) => (
168
+ <TableRow {...getRowProps({ row: r })} key={r.id}>
169
+ {r.cells.map((cell) =>
170
+ cell.info.header === 'actions' ? (
171
+ <TableCell key={cell.id}>
172
+ <Button
173
+ kind="ghost"
174
+ size="sm"
175
+ renderIcon={ArrowRight}
176
+ onClick={() =>
177
+ navigate({ to: `\${openmrsSpaBase}/patient/${r.id}/chart` })
178
+ }
179
+ >
180
+ {t('openChart', 'Open chart')}
181
+ </Button>
182
+ </TableCell>
183
+ ) : (
184
+ <TableCell key={cell.id}>{cell.value}</TableCell>
185
+ ),
186
+ )}
187
+ </TableRow>
188
+ ))}
189
+ </TableBody>
190
+ </Table>
191
+ </TableContainer>
192
+ )}
193
+ </DataTable>
184
194
  </div>
185
195
  );
186
196
  };
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
3
3
  import { InlineLoading } from '@carbon/react';
4
4
  import { navigate, useConfig, useSession } from '@openmrs/esm-framework';
5
5
  import type { PostRegistrationRedirectConfig } from './config-schema';
6
+ import { addRegisteredPatient } from './registered-patients-store';
6
7
 
7
8
  /**
8
9
  * Acts as a one-shot redirect target for the patient registration app.
@@ -30,6 +31,13 @@ const PostRegistrationRedirect: React.FC = () => {
30
31
  (role) => role.trim().toLowerCase() === config.registrationClerkRoleName.trim().toLowerCase(),
31
32
  );
32
33
 
34
+ // Record the freshly-registered patient against the current user so the
35
+ // My Registered Patients list can show them (server-side filtering by
36
+ // creator is not supported on the encounter REST endpoint).
37
+ if (patientUuid && session.user.uuid) {
38
+ addRegisteredPatient(session.user.uuid, patientUuid);
39
+ }
40
+
33
41
  if (isRegistrationClerk) {
34
42
  navigate({ to: `\${openmrsSpaBase}/${config.registeredPatientsListPath}` });
35
43
  } else if (patientUuid) {
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Local persistent record of patients each user has registered.
3
+ *
4
+ * The OpenMRS REST encounter search endpoint requires a `patient` parameter,
5
+ * so we cannot list "all encounters of type X created by user Y" server-side.
6
+ * As a pragmatic MVP we keep a per-user list of registered patient UUIDs in
7
+ * localStorage and fetch each patient by UUID when rendering the list.
8
+ *
9
+ * Limitations: list is per-browser and per-device. Clearing site data wipes
10
+ * it. Switching browsers shows different lists. Acceptable for a single-clerk
11
+ * registration kiosk.
12
+ */
13
+
14
+ const STORAGE_KEY = 'clinicemr.registeredPatients';
15
+ const MAX_ENTRIES_PER_USER = 200;
16
+
17
+ interface RegisteredPatientEntry {
18
+ patientUuid: string;
19
+ registeredAt: string; // ISO timestamp
20
+ }
21
+
22
+ interface StoreShape {
23
+ // map: userUuid -> entries (most recent first)
24
+ [userUuid: string]: RegisteredPatientEntry[];
25
+ }
26
+
27
+ function readStore(): StoreShape {
28
+ try {
29
+ const raw = window.localStorage.getItem(STORAGE_KEY);
30
+ if (!raw) return {};
31
+ const parsed = JSON.parse(raw);
32
+ return parsed && typeof parsed === 'object' ? parsed : {};
33
+ } catch {
34
+ return {};
35
+ }
36
+ }
37
+
38
+ function writeStore(store: StoreShape): void {
39
+ try {
40
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
41
+ } catch {
42
+ // ignore quota errors
43
+ }
44
+ }
45
+
46
+ export function addRegisteredPatient(userUuid: string, patientUuid: string): void {
47
+ if (!userUuid || !patientUuid) return;
48
+ const store = readStore();
49
+ const existing = store[userUuid] ?? [];
50
+ // Move/insert this patient to the front, dedup
51
+ const filtered = existing.filter((e) => e.patientUuid !== patientUuid);
52
+ filtered.unshift({ patientUuid, registeredAt: new Date().toISOString() });
53
+ store[userUuid] = filtered.slice(0, MAX_ENTRIES_PER_USER);
54
+ writeStore(store);
55
+ }
56
+
57
+ export function getRegisteredPatients(userUuid: string): RegisteredPatientEntry[] {
58
+ if (!userUuid) return [];
59
+ const store = readStore();
60
+ return store[userUuid] ?? [];
61
+ }