@clinicemr/esm-post-registration-redirect-app 1.1.0 → 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 +1 -1
- package/dist/918.js +1 -1
- package/dist/openmrs-esm-post-registration-redirect-app.js.buildmanifest.json +6 -6
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/my-registered-patients.component.tsx +114 -96
- package/src/post-registration-redirect.component.tsx +8 -0
- package/src/registered-patients-store.ts +61 -0
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:()=>
|
|
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,
|
|
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":
|
|
331
|
+
"size": 19326,
|
|
332
332
|
"sizes": {
|
|
333
|
-
"javascript":
|
|
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": "
|
|
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":
|
|
403
|
+
"size": 5679,
|
|
404
404
|
"sizes": {
|
|
405
|
-
"javascript":
|
|
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": "
|
|
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
|
+
{"$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.
|
|
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,30 +25,38 @@ 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
|
|
31
|
+
interface PatientResource {
|
|
31
32
|
uuid: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
38
|
+
const patientRep =
|
|
39
|
+
'custom:(uuid,display,identifiers:(identifier,preferred),person:(gender,age))';
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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);
|
|
59
|
+
}
|
|
52
60
|
|
|
53
61
|
const MyRegisteredPatients: React.FC = () => {
|
|
54
62
|
const { t } = useTranslation();
|
|
@@ -56,40 +64,44 @@ const MyRegisteredPatients: React.FC = () => {
|
|
|
56
64
|
const config = useConfig<PostRegistrationRedirectConfig>();
|
|
57
65
|
const currentUserUuid = session?.user?.uuid;
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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;
|
|
64
76
|
|
|
65
|
-
const { data, error, isLoading } = useSWR
|
|
77
|
+
const { data, error, isLoading } = useSWR(swrKey, () => fetchPatients(entries.map((e) => e.patientUuid)));
|
|
66
78
|
|
|
67
79
|
const rows = useMemo(() => {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}, [data,
|
|
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]);
|
|
93
105
|
|
|
94
106
|
const headers = [
|
|
95
107
|
{ key: 'name', header: t('patientName', 'Name') },
|
|
@@ -100,11 +112,22 @@ const MyRegisteredPatients: React.FC = () => {
|
|
|
100
112
|
{ key: 'actions', header: '' },
|
|
101
113
|
];
|
|
102
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
|
+
|
|
103
126
|
if (isLoading) {
|
|
104
127
|
return (
|
|
105
128
|
<div className={styles.container}>
|
|
106
129
|
<h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
|
|
107
|
-
<DataTableSkeleton headers={headers} rowCount={5} />
|
|
130
|
+
<DataTableSkeleton headers={headers} rowCount={Math.min(entries.length, 5)} />
|
|
108
131
|
</div>
|
|
109
132
|
);
|
|
110
133
|
}
|
|
@@ -112,6 +135,7 @@ const MyRegisteredPatients: React.FC = () => {
|
|
|
112
135
|
if (error) {
|
|
113
136
|
return (
|
|
114
137
|
<div className={styles.container}>
|
|
138
|
+
<h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
|
|
115
139
|
<InlineNotification
|
|
116
140
|
kind="error"
|
|
117
141
|
title={t('errorLoading', "Could not load patients you've registered.")}
|
|
@@ -126,53 +150,47 @@ const MyRegisteredPatients: React.FC = () => {
|
|
|
126
150
|
return (
|
|
127
151
|
<div className={styles.container}>
|
|
128
152
|
<h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
|
|
129
|
-
{rows
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<TableRow>
|
|
140
|
-
{dataHeaders.map((h) => (
|
|
141
|
-
<TableHeader {...getHeaderProps({ header: h })} key={h.key}>
|
|
142
|
-
{h.header}
|
|
143
|
-
</TableHeader>
|
|
144
|
-
))}
|
|
145
|
-
</TableRow>
|
|
146
|
-
</TableHead>
|
|
147
|
-
<TableBody>
|
|
148
|
-
{dataRows.map((r) => (
|
|
149
|
-
<TableRow {...getRowProps({ row: r })} key={r.id}>
|
|
150
|
-
{r.cells.map((cell) =>
|
|
151
|
-
cell.info.header === 'actions' ? (
|
|
152
|
-
<TableCell key={cell.id}>
|
|
153
|
-
<Button
|
|
154
|
-
kind="ghost"
|
|
155
|
-
size="sm"
|
|
156
|
-
renderIcon={ArrowRight}
|
|
157
|
-
onClick={() =>
|
|
158
|
-
navigate({ to: `\${openmrsSpaBase}/patient/${r.id}/chart` })
|
|
159
|
-
}
|
|
160
|
-
>
|
|
161
|
-
{t('openChart', 'Open chart')}
|
|
162
|
-
</Button>
|
|
163
|
-
</TableCell>
|
|
164
|
-
) : (
|
|
165
|
-
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
166
|
-
),
|
|
167
|
-
)}
|
|
168
|
-
</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>
|
|
169
163
|
))}
|
|
170
|
-
</
|
|
171
|
-
</
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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>
|
|
176
194
|
</div>
|
|
177
195
|
);
|
|
178
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
|
+
}
|