@clinicemr/esm-post-registration-redirect-app 1.0.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.
@@ -0,0 +1,399 @@
1
+ {
2
+ "chunks": [
3
+ {
4
+ "rendered": true,
5
+ "initial": false,
6
+ "entry": false,
7
+ "recorded": false,
8
+ "size": 7120,
9
+ "sizes": {
10
+ "javascript": 7120
11
+ },
12
+ "names": [],
13
+ "idHints": [],
14
+ "runtime": [
15
+ "@clinicemr/esm-post-registration-redirect-app",
16
+ "main"
17
+ ],
18
+ "files": [
19
+ "41.js"
20
+ ],
21
+ "auxiliaryFiles": [],
22
+ "hash": "78b7a5c1ca79c948",
23
+ "childrenByOrder": {}
24
+ },
25
+ {
26
+ "rendered": true,
27
+ "initial": false,
28
+ "entry": false,
29
+ "recorded": false,
30
+ "size": 2016,
31
+ "sizes": {
32
+ "consume-shared": 42,
33
+ "javascript": 1974
34
+ },
35
+ "names": [],
36
+ "idHints": [],
37
+ "runtime": [
38
+ "@clinicemr/esm-post-registration-redirect-app"
39
+ ],
40
+ "files": [
41
+ "173.js"
42
+ ],
43
+ "auxiliaryFiles": [],
44
+ "hash": "9749c56e8753e206",
45
+ "childrenByOrder": {}
46
+ },
47
+ {
48
+ "rendered": false,
49
+ "initial": false,
50
+ "entry": false,
51
+ "recorded": false,
52
+ "size": 126,
53
+ "sizes": {
54
+ "consume-shared": 126
55
+ },
56
+ "names": [],
57
+ "idHints": [],
58
+ "runtime": [
59
+ "@clinicemr/esm-post-registration-redirect-app",
60
+ "main"
61
+ ],
62
+ "files": [],
63
+ "auxiliaryFiles": [],
64
+ "hash": "7338eb0dde7b075c",
65
+ "childrenByOrder": {}
66
+ },
67
+ {
68
+ "rendered": true,
69
+ "initial": false,
70
+ "entry": false,
71
+ "recorded": false,
72
+ "size": 347,
73
+ "sizes": {
74
+ "javascript": 347
75
+ },
76
+ "names": [],
77
+ "idHints": [],
78
+ "runtime": [
79
+ "@clinicemr/esm-post-registration-redirect-app",
80
+ "main"
81
+ ],
82
+ "files": [
83
+ "300.js"
84
+ ],
85
+ "auxiliaryFiles": [],
86
+ "hash": "c31905dc24e19f4e",
87
+ "childrenByOrder": {}
88
+ },
89
+ {
90
+ "rendered": true,
91
+ "initial": false,
92
+ "entry": false,
93
+ "recorded": false,
94
+ "reason": "reused as split chunk (cache group: defaultVendors)",
95
+ "size": 29620,
96
+ "sizes": {
97
+ "javascript": 29620
98
+ },
99
+ "names": [],
100
+ "idHints": [
101
+ "vendors"
102
+ ],
103
+ "runtime": [
104
+ "@clinicemr/esm-post-registration-redirect-app",
105
+ "main"
106
+ ],
107
+ "files": [
108
+ "336.js"
109
+ ],
110
+ "auxiliaryFiles": [],
111
+ "hash": "d7e3483342014979",
112
+ "childrenByOrder": {}
113
+ },
114
+ {
115
+ "rendered": false,
116
+ "initial": false,
117
+ "entry": false,
118
+ "recorded": false,
119
+ "reason": "split chunk (cache group: default)",
120
+ "size": 42,
121
+ "sizes": {
122
+ "consume-shared": 42
123
+ },
124
+ "names": [],
125
+ "idHints": [],
126
+ "runtime": [
127
+ "@clinicemr/esm-post-registration-redirect-app",
128
+ "main"
129
+ ],
130
+ "files": [],
131
+ "auxiliaryFiles": [],
132
+ "hash": "1bb89edfb4edb626",
133
+ "childrenByOrder": {}
134
+ },
135
+ {
136
+ "rendered": false,
137
+ "initial": false,
138
+ "entry": false,
139
+ "recorded": false,
140
+ "reason": "reused as split chunk (cache group: default)",
141
+ "size": 42,
142
+ "sizes": {
143
+ "consume-shared": 42
144
+ },
145
+ "names": [],
146
+ "idHints": [],
147
+ "runtime": [
148
+ "@clinicemr/esm-post-registration-redirect-app",
149
+ "main"
150
+ ],
151
+ "files": [],
152
+ "auxiliaryFiles": [],
153
+ "hash": "4129e178f5788508",
154
+ "childrenByOrder": {}
155
+ },
156
+ {
157
+ "rendered": true,
158
+ "initial": false,
159
+ "entry": false,
160
+ "recorded": false,
161
+ "reason": "reused as split chunk (cache group: defaultVendors)",
162
+ "size": 163792,
163
+ "sizes": {
164
+ "javascript": 163792
165
+ },
166
+ "names": [],
167
+ "idHints": [
168
+ "vendors"
169
+ ],
170
+ "runtime": [
171
+ "@clinicemr/esm-post-registration-redirect-app",
172
+ "main"
173
+ ],
174
+ "files": [
175
+ "457.js"
176
+ ],
177
+ "auxiliaryFiles": [],
178
+ "hash": "528ca67c284a137c",
179
+ "childrenByOrder": {}
180
+ },
181
+ {
182
+ "rendered": true,
183
+ "initial": false,
184
+ "entry": false,
185
+ "recorded": false,
186
+ "size": 7160,
187
+ "sizes": {
188
+ "javascript": 7160
189
+ },
190
+ "names": [],
191
+ "idHints": [],
192
+ "runtime": [
193
+ "@clinicemr/esm-post-registration-redirect-app",
194
+ "main"
195
+ ],
196
+ "files": [
197
+ "464.js"
198
+ ],
199
+ "auxiliaryFiles": [],
200
+ "hash": "1c31241741414554",
201
+ "childrenByOrder": {}
202
+ },
203
+ {
204
+ "rendered": true,
205
+ "initial": true,
206
+ "entry": true,
207
+ "recorded": false,
208
+ "size": 20526,
209
+ "sizes": {
210
+ "javascript": 42,
211
+ "share-init": 252,
212
+ "runtime": 20232
213
+ },
214
+ "names": [
215
+ "@clinicemr/esm-post-registration-redirect-app"
216
+ ],
217
+ "idHints": [],
218
+ "runtime": [
219
+ "@clinicemr/esm-post-registration-redirect-app"
220
+ ],
221
+ "files": [
222
+ "openmrs-esm-post-registration-redirect-app.js"
223
+ ],
224
+ "auxiliaryFiles": [],
225
+ "hash": "f752fd7156554001",
226
+ "childrenByOrder": {}
227
+ },
228
+ {
229
+ "rendered": true,
230
+ "initial": false,
231
+ "entry": false,
232
+ "recorded": false,
233
+ "reason": "split chunk (cache group: defaultVendors)",
234
+ "size": 59460,
235
+ "sizes": {
236
+ "javascript": 59460
237
+ },
238
+ "names": [],
239
+ "idHints": [
240
+ "vendors"
241
+ ],
242
+ "runtime": [
243
+ "@clinicemr/esm-post-registration-redirect-app",
244
+ "main"
245
+ ],
246
+ "files": [
247
+ "686.js"
248
+ ],
249
+ "auxiliaryFiles": [],
250
+ "hash": "74e5576db4c8618e",
251
+ "childrenByOrder": {}
252
+ },
253
+ {
254
+ "rendered": true,
255
+ "initial": false,
256
+ "entry": false,
257
+ "recorded": false,
258
+ "reason": "split chunk (cache group: defaultVendors)",
259
+ "size": 3101747,
260
+ "sizes": {
261
+ "javascript": 3101747
262
+ },
263
+ "names": [],
264
+ "idHints": [
265
+ "vendors"
266
+ ],
267
+ "runtime": [
268
+ "@clinicemr/esm-post-registration-redirect-app",
269
+ "main"
270
+ ],
271
+ "files": [
272
+ "777.js"
273
+ ],
274
+ "auxiliaryFiles": [],
275
+ "hash": "501a19fa12f12d76",
276
+ "childrenByOrder": {}
277
+ },
278
+ {
279
+ "rendered": true,
280
+ "initial": true,
281
+ "entry": true,
282
+ "recorded": false,
283
+ "size": 22888,
284
+ "sizes": {
285
+ "consume-shared": 42,
286
+ "javascript": 1974,
287
+ "share-init": 252,
288
+ "runtime": 20620
289
+ },
290
+ "names": [
291
+ "main"
292
+ ],
293
+ "idHints": [],
294
+ "runtime": [
295
+ "main"
296
+ ],
297
+ "files": [
298
+ "main.js"
299
+ ],
300
+ "auxiliaryFiles": [],
301
+ "hash": "aaf4bda84c2840f3",
302
+ "childrenByOrder": {}
303
+ },
304
+ {
305
+ "rendered": true,
306
+ "initial": false,
307
+ "entry": false,
308
+ "recorded": false,
309
+ "size": 13312,
310
+ "sizes": {
311
+ "javascript": 13312
312
+ },
313
+ "names": [],
314
+ "idHints": [],
315
+ "runtime": [
316
+ "@clinicemr/esm-post-registration-redirect-app",
317
+ "main"
318
+ ],
319
+ "files": [
320
+ "802.js"
321
+ ],
322
+ "auxiliaryFiles": [],
323
+ "hash": "f3a9cf5c36cc74e7",
324
+ "childrenByOrder": {}
325
+ },
326
+ {
327
+ "rendered": true,
328
+ "initial": false,
329
+ "entry": false,
330
+ "recorded": false,
331
+ "reason": "split chunk (cache group: defaultVendors)",
332
+ "size": 2391417,
333
+ "sizes": {
334
+ "javascript": 2391417
335
+ },
336
+ "names": [],
337
+ "idHints": [
338
+ "vendors"
339
+ ],
340
+ "runtime": [
341
+ "@clinicemr/esm-post-registration-redirect-app",
342
+ "main"
343
+ ],
344
+ "files": [
345
+ "803.js"
346
+ ],
347
+ "auxiliaryFiles": [],
348
+ "hash": "5bbbe5cc46a42f5f",
349
+ "childrenByOrder": {}
350
+ },
351
+ {
352
+ "rendered": true,
353
+ "initial": false,
354
+ "entry": false,
355
+ "recorded": false,
356
+ "reason": "reused as split chunk (cache group: defaultVendors)",
357
+ "size": 251616,
358
+ "sizes": {
359
+ "javascript": 251616
360
+ },
361
+ "names": [],
362
+ "idHints": [
363
+ "vendors"
364
+ ],
365
+ "runtime": [
366
+ "@clinicemr/esm-post-registration-redirect-app",
367
+ "main"
368
+ ],
369
+ "files": [
370
+ "913.js"
371
+ ],
372
+ "auxiliaryFiles": [],
373
+ "hash": "5611439d239d8885",
374
+ "childrenByOrder": {}
375
+ },
376
+ {
377
+ "rendered": true,
378
+ "initial": false,
379
+ "entry": false,
380
+ "recorded": false,
381
+ "size": 3077,
382
+ "sizes": {
383
+ "javascript": 3077
384
+ },
385
+ "names": [],
386
+ "idHints": [],
387
+ "runtime": [
388
+ "@clinicemr/esm-post-registration-redirect-app",
389
+ "main"
390
+ ],
391
+ "files": [
392
+ "918.js"
393
+ ],
394
+ "auxiliaryFiles": [],
395
+ "hash": "a2d38887225f4d7b",
396
+ "childrenByOrder": {}
397
+ }
398
+ ]
399
+ }
@@ -0,0 +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":[],"version":"1.0.0"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@clinicemr/esm-post-registration-redirect-app",
3
+ "version": "1.0.0",
4
+ "license": "MPL-2.0",
5
+ "description": "Role-aware post-registration redirect for OpenMRS SSUUBO distro",
6
+ "browser": "dist/openmrs-esm-post-registration-redirect-app.js",
7
+ "main": "src/index.ts",
8
+ "source": true,
9
+ "scripts": {
10
+ "start": "openmrs develop",
11
+ "serve": "webpack serve --mode=development",
12
+ "build": "webpack --mode production",
13
+ "lint": "eslint src --ext js,jsx,ts,tsx --max-warnings 0",
14
+ "typescript": "tsc",
15
+ "test": "jest --config jest.config.js --passWithNoTests",
16
+ "extract-translations": "i18next 'src/**/*.component.tsx' --config ./tools/i18next-parser.config.js"
17
+ },
18
+ "browserslist": [
19
+ "extends browserslist-config-openmrs"
20
+ ],
21
+ "keywords": [
22
+ "openmrs",
23
+ "microfrontends"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/bwongo-digital-solutions/openmrs-clinicemr-esm-patient-chart.git"
28
+ },
29
+ "homepage": "https://github.com/bwongo-digital-solutions/openmrs-clinicemr-esm-patient-chart#readme",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@carbon/react": "^1.83.0",
35
+ "lodash-es": "^4.17.21"
36
+ },
37
+ "peerDependencies": {
38
+ "@openmrs/esm-framework": "*",
39
+ "dayjs": "1.x",
40
+ "react": "18.x",
41
+ "react-i18next": "16.x",
42
+ "react-router-dom": "6.x",
43
+ "rxjs": "6.x"
44
+ },
45
+ "packageManager": "yarn@4.10.3"
46
+ }
@@ -0,0 +1,33 @@
1
+ import { Type } from '@openmrs/esm-framework';
2
+
3
+ export const configSchema = {
4
+ registrationClerkRoleName: {
5
+ _type: Type.String,
6
+ _default: 'Organizational: Registration Clerk',
7
+ _description:
8
+ 'Name of the OpenMRS role that, when held by the current user, makes the post-registration page redirect to the My Registered Patients list instead of the patient chart.',
9
+ },
10
+ registeredPatientsListPath: {
11
+ _type: Type.String,
12
+ _default: 'my-registered-patients',
13
+ _description: 'SPA-relative path to redirect Registration Clerks to after a successful registration.',
14
+ },
15
+ registrationEncounterTypeUuid: {
16
+ _type: Type.String,
17
+ _default: 'dd528487-82a5-4082-9c72-ed246bd49591',
18
+ _description:
19
+ 'UUID of the encounter type recorded during patient registration. Used to look up patients registered by the current user.',
20
+ },
21
+ pageSize: {
22
+ _type: Type.Number,
23
+ _default: 25,
24
+ _description: 'Maximum number of recent registrations to load for the My Registered Patients list.',
25
+ },
26
+ };
27
+
28
+ export interface PostRegistrationRedirectConfig {
29
+ registrationClerkRoleName: string;
30
+ registeredPatientsListPath: string;
31
+ registrationEncounterTypeUuid: string;
32
+ pageSize: number;
33
+ }
@@ -0,0 +1,3 @@
1
+ declare module '*.css';
2
+ declare module '*.scss';
3
+ declare module '*.png';
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { getAsyncLifecycle, defineConfigSchema } from '@openmrs/esm-framework';
2
+ import { configSchema } from './config-schema';
3
+
4
+ const moduleName = '@clinicemr/esm-post-registration-redirect-app';
5
+
6
+ const options = {
7
+ featureName: 'post-registration-redirect',
8
+ moduleName,
9
+ };
10
+
11
+ export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
12
+
13
+ export function startupApp() {
14
+ defineConfigSchema(moduleName, configSchema);
15
+ }
16
+
17
+ export const postRegistrationRedirect = getAsyncLifecycle(
18
+ () => import('./post-registration-redirect.component'),
19
+ options,
20
+ );
21
+
22
+ export const myRegisteredPatients = getAsyncLifecycle(
23
+ () => import('./my-registered-patients.component'),
24
+ options,
25
+ );
@@ -0,0 +1,180 @@
1
+ import React, { useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import useSWR from 'swr';
4
+ import {
5
+ DataTable,
6
+ DataTableSkeleton,
7
+ InlineNotification,
8
+ Table,
9
+ TableBody,
10
+ TableCell,
11
+ TableContainer,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ Tile,
16
+ Button,
17
+ } from '@carbon/react';
18
+ import { ArrowRight } from '@carbon/icons-react';
19
+ import {
20
+ navigate,
21
+ openmrsFetch,
22
+ restBaseUrl,
23
+ useConfig,
24
+ useSession,
25
+ formatDate,
26
+ } from '@openmrs/esm-framework';
27
+ import type { PostRegistrationRedirectConfig } from './config-schema';
28
+ import styles from './my-registered-patients.scss';
29
+
30
+ interface RegistrationEncounter {
31
+ 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
+ };
42
+ }
43
+
44
+ interface EncounterListResponse {
45
+ results: RegistrationEncounter[];
46
+ }
47
+
48
+ const customRep =
49
+ 'custom:(uuid,encounterDatetime,' +
50
+ 'patient:(uuid,display,identifiers:(identifier,preferred),person:(gender,age)),' +
51
+ 'auditInfo:(creator:(uuid,display)))';
52
+
53
+ const MyRegisteredPatients: React.FC = () => {
54
+ const { t } = useTranslation();
55
+ const session = useSession();
56
+ const config = useConfig<PostRegistrationRedirectConfig>();
57
+ const currentUserUuid = session?.user?.uuid;
58
+
59
+ const url =
60
+ currentUserUuid && config.registrationEncounterTypeUuid
61
+ ? `${restBaseUrl}/encounter?encounterType=${config.registrationEncounterTypeUuid}` +
62
+ `&v=${encodeURIComponent(customRep)}&limit=${Math.max(50, config.pageSize * 4)}`
63
+ : null;
64
+
65
+ const { data, error, isLoading } = useSWR<{ data: EncounterListResponse }>(url, openmrsFetch);
66
+
67
+ const rows = useMemo(() => {
68
+ const encounters = data?.data?.results ?? [];
69
+ const mine = encounters.filter((e) => e.auditInfo?.creator?.uuid === currentUserUuid);
70
+ // Dedupe by patient uuid, keeping the most recent encounter date
71
+ const byPatient = new Map<string, RegistrationEncounter>();
72
+ for (const e of mine) {
73
+ const existing = byPatient.get(e.patient.uuid);
74
+ if (!existing || (e.encounterDatetime ?? '') > (existing.encounterDatetime ?? '')) {
75
+ byPatient.set(e.patient.uuid, e);
76
+ }
77
+ }
78
+ const list = Array.from(byPatient.values()).sort((a, b) =>
79
+ (b.encounterDatetime ?? '').localeCompare(a.encounterDatetime ?? ''),
80
+ );
81
+ return list.slice(0, config.pageSize).map((e) => {
82
+ const preferredId = e.patient.identifiers?.find((i) => i.preferred) ?? e.patient.identifiers?.[0];
83
+ return {
84
+ id: e.patient.uuid,
85
+ name: e.patient.display,
86
+ identifier: preferredId?.identifier ?? '—',
87
+ gender: e.patient.person?.gender ?? '—',
88
+ age: e.patient.person?.age != null ? String(e.patient.person.age) : '—',
89
+ registeredOn: e.encounterDatetime ? formatDate(new Date(e.encounterDatetime)) : '—',
90
+ };
91
+ });
92
+ }, [data, currentUserUuid, config.pageSize]);
93
+
94
+ const headers = [
95
+ { key: 'name', header: t('patientName', 'Name') },
96
+ { key: 'identifier', header: t('identifier', 'Identifier') },
97
+ { key: 'gender', header: t('gender', 'Gender') },
98
+ { key: 'age', header: t('age', 'Age') },
99
+ { key: 'registeredOn', header: t('registeredOn', 'Registered on') },
100
+ { key: 'actions', header: '' },
101
+ ];
102
+
103
+ if (isLoading) {
104
+ return (
105
+ <div className={styles.container}>
106
+ <h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
107
+ <DataTableSkeleton headers={headers} rowCount={5} />
108
+ </div>
109
+ );
110
+ }
111
+
112
+ if (error) {
113
+ return (
114
+ <div className={styles.container}>
115
+ <InlineNotification
116
+ kind="error"
117
+ title={t('errorLoading', "Could not load patients you've registered.")}
118
+ subtitle={String((error as Error)?.message ?? '')}
119
+ lowContrast
120
+ hideCloseButton
121
+ />
122
+ </div>
123
+ );
124
+ }
125
+
126
+ return (
127
+ <div className={styles.container}>
128
+ <h2 className={styles.title}>{t('myRegisteredPatients', 'My Registered Patients')}</h2>
129
+ {rows.length === 0 ? (
130
+ <Tile className={styles.emptyTile}>
131
+ <p>{t('noPatientsRegistered', "You haven't registered any patients yet.")}</p>
132
+ </Tile>
133
+ ) : (
134
+ <DataTable rows={rows} headers={headers} useZebraStyles>
135
+ {({ rows: dataRows, headers: dataHeaders, getHeaderProps, getRowProps, getTableProps }) => (
136
+ <TableContainer>
137
+ <Table {...getTableProps()}>
138
+ <TableHead>
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>
169
+ ))}
170
+ </TableBody>
171
+ </Table>
172
+ </TableContainer>
173
+ )}
174
+ </DataTable>
175
+ )}
176
+ </div>
177
+ );
178
+ };
179
+
180
+ export default MyRegisteredPatients;