@pih/esm-audit-app 2.1.1-pre.144 → 2.1.1-pre.151

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.
Files changed (48) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/README.md +96 -2
  3. package/dist/115.js +1 -1
  4. package/dist/117.js +1 -1
  5. package/dist/117.js.map +1 -1
  6. package/dist/508.js +1 -1
  7. package/dist/508.js.map +1 -1
  8. package/dist/514.js +1 -0
  9. package/dist/514.js.map +1 -0
  10. package/dist/912.js +1 -1
  11. package/dist/912.js.map +1 -1
  12. package/dist/main.js +1 -1
  13. package/dist/pih-esm-audit-app.js +1 -1
  14. package/dist/pih-esm-audit-app.js.buildmanifest.json +34 -34
  15. package/dist/routes.json +1 -1
  16. package/locales/en/translation.json +118 -0
  17. package/locales/fr/translation.json +118 -0
  18. package/package.json +1 -1
  19. package/src/audit/audit-search.component.tsx +58 -0
  20. package/src/audit/audit.component.tsx +63 -16
  21. package/src/audit/audit.resource.ts +445 -10
  22. package/src/audit/audit.scss +6 -0
  23. package/src/audit/date-range-picker.test-stub.tsx +40 -0
  24. package/src/audit/date-range.test.ts +128 -0
  25. package/src/audit/date-range.ts +120 -0
  26. package/src/audit/encounter-filters.component.tsx +40 -18
  27. package/src/audit/encounter-filters.test.ts +1 -2
  28. package/src/audit/encounter-filters.ts +2 -27
  29. package/src/audit/patient-record.component.test.tsx +3 -5
  30. package/src/audit/patient-record.component.tsx +13 -2
  31. package/src/audit/patient-search.component.tsx +1 -1
  32. package/src/audit/provider-encounters.component.test.tsx +254 -0
  33. package/src/audit/provider-encounters.component.tsx +160 -0
  34. package/src/audit/provider-search.component.test.tsx +72 -0
  35. package/src/audit/provider-search.component.tsx +116 -0
  36. package/src/audit/user-encounters.component.test.tsx +431 -0
  37. package/src/audit/user-encounters.component.tsx +239 -0
  38. package/src/audit/user-encounters.test.ts +176 -0
  39. package/src/audit/user-encounters.ts +166 -0
  40. package/src/audit/user-search.component.test.tsx +89 -0
  41. package/src/audit/user-search.component.tsx +111 -0
  42. package/src/root.component.test.tsx +48 -3
  43. package/src/types.ts +34 -0
  44. package/translations/en.json +29 -0
  45. package/translations/es.json +29 -0
  46. package/translations/fr.json +29 -0
  47. package/dist/446.js +0 -1
  48. package/dist/446.js.map +0 -1
@@ -21,7 +21,7 @@
21
21
  "auxiliaryFiles": [
22
22
  "115.js.map"
23
23
  ],
24
- "hash": "91fcdd326c84f41f",
24
+ "hash": "9b41c7e832a25bd9",
25
25
  "childrenByOrder": {}
26
26
  },
27
27
  {
@@ -29,9 +29,9 @@
29
29
  "rendered": true,
30
30
  "initial": false,
31
31
  "entry": false,
32
- "size": 3364,
32
+ "size": 5110,
33
33
  "sizes": {
34
- "javascript": 3364
34
+ "javascript": 5110
35
35
  },
36
36
  "names": [],
37
37
  "idHints": [],
@@ -45,7 +45,7 @@
45
45
  "auxiliaryFiles": [
46
46
  "117.js.map"
47
47
  ],
48
- "hash": "8b233dccf540208a",
48
+ "hash": "0882b3e8a79e8e83",
49
49
  "childrenByOrder": {}
50
50
  },
51
51
  {
@@ -73,7 +73,7 @@
73
73
  "auxiliaryFiles": [
74
74
  "pih-esm-audit-app.js.map"
75
75
  ],
76
- "hash": "04e926524843a4d0",
76
+ "hash": "023ea685c1be22f5",
77
77
  "childrenByOrder": {}
78
78
  },
79
79
  {
@@ -155,23 +155,26 @@
155
155
  "rendered": true,
156
156
  "initial": false,
157
157
  "entry": false,
158
- "size": 168384,
158
+ "reason": "split chunk (cache group: defaultVendors)",
159
+ "size": 1408999,
159
160
  "sizes": {
160
- "javascript": 168384
161
+ "javascript": 1408999
161
162
  },
162
163
  "names": [],
163
- "idHints": [],
164
+ "idHints": [
165
+ "vendors"
166
+ ],
164
167
  "runtime": [
165
168
  "@pih/esm-audit-app",
166
169
  "main"
167
170
  ],
168
171
  "files": [
169
- "446.js"
172
+ "455.js"
170
173
  ],
171
174
  "auxiliaryFiles": [
172
- "446.js.map"
175
+ "455.js.map"
173
176
  ],
174
- "hash": "2260bb51ed561074",
177
+ "hash": "5342a8efbabac44a",
175
178
  "childrenByOrder": {}
176
179
  },
177
180
  {
@@ -179,26 +182,23 @@
179
182
  "rendered": true,
180
183
  "initial": false,
181
184
  "entry": false,
182
- "reason": "split chunk (cache group: defaultVendors)",
183
- "size": 1408999,
185
+ "size": 7103,
184
186
  "sizes": {
185
- "javascript": 1408999
187
+ "javascript": 7103
186
188
  },
187
189
  "names": [],
188
- "idHints": [
189
- "vendors"
190
- ],
190
+ "idHints": [],
191
191
  "runtime": [
192
192
  "@pih/esm-audit-app",
193
193
  "main"
194
194
  ],
195
195
  "files": [
196
- "455.js"
196
+ "466.js"
197
197
  ],
198
198
  "auxiliaryFiles": [
199
- "455.js.map"
199
+ "466.js.map"
200
200
  ],
201
- "hash": "5342a8efbabac44a",
201
+ "hash": "c30f3521edea15c4",
202
202
  "childrenByOrder": {}
203
203
  },
204
204
  {
@@ -206,9 +206,9 @@
206
206
  "rendered": true,
207
207
  "initial": false,
208
208
  "entry": false,
209
- "size": 7103,
209
+ "size": 6134,
210
210
  "sizes": {
211
- "javascript": 7103
211
+ "javascript": 6134
212
212
  },
213
213
  "names": [],
214
214
  "idHints": [],
@@ -217,12 +217,12 @@
217
217
  "main"
218
218
  ],
219
219
  "files": [
220
- "466.js"
220
+ "508.js"
221
221
  ],
222
222
  "auxiliaryFiles": [
223
- "466.js.map"
223
+ "508.js.map"
224
224
  ],
225
- "hash": "c30f3521edea15c4",
225
+ "hash": "3b9069416bf40695",
226
226
  "childrenByOrder": {}
227
227
  },
228
228
  {
@@ -230,9 +230,9 @@
230
230
  "rendered": true,
231
231
  "initial": false,
232
232
  "entry": false,
233
- "size": 3879,
233
+ "size": 250106,
234
234
  "sizes": {
235
- "javascript": 3879
235
+ "javascript": 250106
236
236
  },
237
237
  "names": [],
238
238
  "idHints": [],
@@ -241,12 +241,12 @@
241
241
  "main"
242
242
  ],
243
243
  "files": [
244
- "508.js"
244
+ "514.js"
245
245
  ],
246
246
  "auxiliaryFiles": [
247
- "508.js.map"
247
+ "514.js.map"
248
248
  ],
249
- "hash": "e05374f843e2bf5a",
249
+ "hash": "d648c72c6a3e4231",
250
250
  "childrenByOrder": {}
251
251
  },
252
252
  {
@@ -474,7 +474,7 @@
474
474
  "auxiliaryFiles": [
475
475
  "main.js.map"
476
476
  ],
477
- "hash": "d57eba330529428c",
477
+ "hash": "b4eb44e7a939436c",
478
478
  "childrenByOrder": {}
479
479
  },
480
480
  {
@@ -482,9 +482,9 @@
482
482
  "rendered": true,
483
483
  "initial": false,
484
484
  "entry": false,
485
- "size": 3773,
485
+ "size": 5812,
486
486
  "sizes": {
487
- "javascript": 3773
487
+ "javascript": 5812
488
488
  },
489
489
  "names": [],
490
490
  "idHints": [],
@@ -498,7 +498,7 @@
498
498
  "auxiliaryFiles": [
499
499
  "912.js.map"
500
500
  ],
501
- "hash": "0d0036fbbb3a7739",
501
+ "hash": "40f746c2367391da",
502
502
  "childrenByOrder": {}
503
503
  },
504
504
  {
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","extensions":[{"name":"audit-dashboard-link","slot":"homepage-dashboard-slot","component":"auditDashboardLink","meta":{"name":"audit","slot":"audit-dashboard-slot","title":"Audit"}},{"name":"audit-dashboard","slot":"audit-dashboard-slot","component":"root"}],"version":"2.1.1-pre.144"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","extensions":[{"name":"audit-dashboard-link","slot":"homepage-dashboard-slot","component":"auditDashboardLink","meta":{"name":"audit","slot":"audit-dashboard-slot","title":"Audit"}},{"name":"audit-dashboard","slot":"audit-dashboard-slot","component":"root"}],"version":"2.1.1-pre.151"}
@@ -0,0 +1,118 @@
1
+ {
2
+ "searchBy": "Search by",
3
+ "patients": "Patients",
4
+ "users": "Users",
5
+ "providers": "Providers",
6
+ "auditTrail": "Audit trail",
7
+ "encounterAudit": "Encounter audit",
8
+ "encounterNotFound": "That encounter could not be found.",
9
+ "patient": "Patient",
10
+ "identifier": "Identifier",
11
+ "encounterDate": "Encounter date",
12
+ "location": "Location",
13
+ "visit": "Visit",
14
+ "encounterType": "Encounter type",
15
+ "form": "Form",
16
+ "createdBy": "Created by",
17
+ "changedBy": "Changed by",
18
+ "deletedBy": "Deleted by",
19
+ "deletionReason": "Reason for deletion",
20
+ "encounter": "Encounter",
21
+ "deleted": "Deleted",
22
+ "encounterDeletedSubtitle": "This encounter has been deleted. Its observations are shown as they were left.",
23
+ "encounterDeleted": "Deleted encounter",
24
+ "encounterSummary": "Encounter summary",
25
+ "noProviders": "No providers are recorded for this encounter.",
26
+ "role": "Role",
27
+ "providerName": "Provider name",
28
+ "backToEncounters": "Back to encounters",
29
+ "allEncounterTypes": "All encounter types",
30
+ "encounterDateRange": "Encounter date range",
31
+ "clearFilters": "Clear filters",
32
+ "edited": "Edited",
33
+ "editedLegend": "the value was changed after it was first recorded",
34
+ "addedLater": "Added later",
35
+ "addedLaterLegend": "recorded after the encounter itself was created",
36
+ "deletedLegend": "removed from the encounter",
37
+ "previousValue": "Previous value",
38
+ "previousValueLegend": "the value that an edit replaced",
39
+ "observations": "Observations",
40
+ "showDeletedObs": "Show deleted observations",
41
+ "showConceptDescriptions": "Show concept descriptions",
42
+ "noObservations": "This encounter has no observations.",
43
+ "questionConcept": "Question concept",
44
+ "value": "Value",
45
+ "change": "Change",
46
+ "recordedBy": "Recorded by",
47
+ "wasPreviously": "was {{value}}",
48
+ "changedByUser": "Changed by {{user}}",
49
+ "encounterCreatedAction": "Created encounter",
50
+ "encounterChangedAction": "Changed encounter",
51
+ "encounterDeletedAction": "Deleted encounter",
52
+ "obsRecordedAction": "Recorded observation",
53
+ "obsEditedAction": "Edited observation",
54
+ "obsDeletedAction": "Deleted observation",
55
+ "recordActivity": "Patient activity",
56
+ "readingObservations": "Reading observations from {{read}} of {{total}} encounters…",
57
+ "noActivity": "There is no recorded activity to show.",
58
+ "whoTouchedThisRecord": "Who modified this patient record",
59
+ "user": "User",
60
+ "encountersCreated": "Encounters created",
61
+ "obsRecordedCount": "Observations recorded",
62
+ "obsEditedCount": "Observations edited",
63
+ "obsDeletedCount": "Observations deleted",
64
+ "firstActivity": "First activity",
65
+ "lastActivity": "Last activity",
66
+ "showOnlyThisUser": "Show only this user's activity",
67
+ "unknownUser": "Unknown user",
68
+ "activityLog": "Activity log",
69
+ "showEveryone": "Show everyone",
70
+ "when": "When",
71
+ "action": "Action",
72
+ "detail": "Detail",
73
+ "encounters": "Encounters",
74
+ "noEncountersMatchFilters": "No encounters match these filters.",
75
+ "noEncountersFound": "This patient has no encounters to audit.",
76
+ "provider": "Provider",
77
+ "status": "Status",
78
+ "backToPatientSearch": "Back to patient search",
79
+ "includeDeletedEncounters": "Include deleted encounters",
80
+ "cannotIncludeDeletedEncounters": "Deleted encounters can only be listed for a patient who has an identifier.",
81
+ "deletedEncountersUnavailable": "Deleted encounters unavailable",
82
+ "auditViews": "Audit views",
83
+ "searchForPatient": "Search for a patient by name or identifier",
84
+ "searchToBegin": "Enter a patient name or identifier to begin auditing their records.",
85
+ "patientSearchResults": "Patient search results",
86
+ "noPatientsFound": "No patients match \"{{searchTerm}}\".",
87
+ "patientName": "Patient name",
88
+ "gender": "Gender",
89
+ "age": "Age",
90
+ "birthdate": "Date of birth",
91
+ "backToProviderSearch": "Back to provider search",
92
+ "providerEncountersScope": "These are the encounters this provider is recorded on, which is not the same as the ones they entered or changed — the last column names who did that, and the Users tab searches by it.",
93
+ "recordedAsProvider": "Recorded as provider",
94
+ "noProviderEncountersMatch": "No encounters for this provider match these filters.",
95
+ "noProviderEncounters": "This provider is not recorded on any encounters.",
96
+ "searchForProvider": "Search for a provider by name or identifier",
97
+ "searchProviderToBegin": "Enter a provider name or identifier to see the encounters they are recorded on.",
98
+ "providerSearchResults": "Provider search results",
99
+ "noProvidersFound": "No providers match \"{{searchTerm}}\".",
100
+ "backToUserSearch": "Back to user search",
101
+ "changedBetween": "Changed between",
102
+ "readingUserObservations": "Read {{obsRead}} observations so far…",
103
+ "noUserEncountersMatch": "This user did not change any observations matching these filters.",
104
+ "noUserEncounters": "This user has not recorded or deleted any observations.",
105
+ "userScanStoppedEarly": "Stopped after reading {{obsRead}} observation changes without filling the page. Narrow the date range to search further back.",
106
+ "partialUserScan": "Part of the trail",
107
+ "lastChanged": "Last changed",
108
+ "lookingForMoreEncounters": "Looking for more encounters…",
109
+ "encounterRange": "Encounters {{from}}–{{to}}",
110
+ "searchForUser": "Search for a user by name or username",
111
+ "searchUserToBegin": "Enter a name or username to see the encounters that user has modified.",
112
+ "userSearchResults": "User search results",
113
+ "noUsersFound": "No users match \"{{searchTerm}}\".",
114
+ "name": "Name",
115
+ "username": "Username",
116
+ "retired": "Retired",
117
+ "audit": ""
118
+ }
@@ -0,0 +1,118 @@
1
+ {
2
+ "searchBy": "Search by",
3
+ "patients": "Patients",
4
+ "users": "Users",
5
+ "providers": "Providers",
6
+ "auditTrail": "Audit trail",
7
+ "encounterAudit": "Encounter audit",
8
+ "encounterNotFound": "That encounter could not be found.",
9
+ "patient": "Patient",
10
+ "identifier": "Identifier",
11
+ "encounterDate": "Encounter date",
12
+ "location": "Location",
13
+ "visit": "Visit",
14
+ "encounterType": "Encounter type",
15
+ "form": "Form",
16
+ "createdBy": "Created by",
17
+ "changedBy": "Changed by",
18
+ "deletedBy": "Deleted by",
19
+ "deletionReason": "Reason for deletion",
20
+ "encounter": "Encounter",
21
+ "deleted": "Deleted",
22
+ "encounterDeletedSubtitle": "This encounter has been deleted. Its observations are shown as they were left.",
23
+ "encounterDeleted": "Deleted encounter",
24
+ "encounterSummary": "Encounter summary",
25
+ "noProviders": "No providers are recorded for this encounter.",
26
+ "role": "Role",
27
+ "providerName": "Provider name",
28
+ "backToEncounters": "Back to encounters",
29
+ "allEncounterTypes": "All encounter types",
30
+ "encounterDateRange": "Encounter date range",
31
+ "clearFilters": "Clear filters",
32
+ "edited": "Edited",
33
+ "editedLegend": "the value was changed after it was first recorded",
34
+ "addedLater": "Added later",
35
+ "addedLaterLegend": "recorded after the encounter itself was created",
36
+ "deletedLegend": "removed from the encounter",
37
+ "previousValue": "Previous value",
38
+ "previousValueLegend": "the value that an edit replaced",
39
+ "observations": "Observations",
40
+ "showDeletedObs": "Show deleted observations",
41
+ "showConceptDescriptions": "Show concept descriptions",
42
+ "noObservations": "This encounter has no observations.",
43
+ "questionConcept": "Question concept",
44
+ "value": "Value",
45
+ "change": "Change",
46
+ "recordedBy": "Recorded by",
47
+ "wasPreviously": "was {{value}}",
48
+ "changedByUser": "Changed by {{user}}",
49
+ "encounterCreatedAction": "Created encounter",
50
+ "encounterChangedAction": "Changed encounter",
51
+ "encounterDeletedAction": "Deleted encounter",
52
+ "obsRecordedAction": "Recorded observation",
53
+ "obsEditedAction": "Edited observation",
54
+ "obsDeletedAction": "Deleted observation",
55
+ "recordActivity": "Patient activity",
56
+ "readingObservations": "Reading observations from {{read}} of {{total}} encounters…",
57
+ "noActivity": "There is no recorded activity to show.",
58
+ "whoTouchedThisRecord": "Who modified this patient record",
59
+ "user": "User",
60
+ "encountersCreated": "Encounters created",
61
+ "obsRecordedCount": "Observations recorded",
62
+ "obsEditedCount": "Observations edited",
63
+ "obsDeletedCount": "Observations deleted",
64
+ "firstActivity": "First activity",
65
+ "lastActivity": "Last activity",
66
+ "showOnlyThisUser": "Show only this user's activity",
67
+ "unknownUser": "Unknown user",
68
+ "activityLog": "Activity log",
69
+ "showEveryone": "Show everyone",
70
+ "when": "When",
71
+ "action": "Action",
72
+ "detail": "Detail",
73
+ "encounters": "Encounters",
74
+ "noEncountersMatchFilters": "No encounters match these filters.",
75
+ "noEncountersFound": "This patient has no encounters to audit.",
76
+ "provider": "Provider",
77
+ "status": "Status",
78
+ "backToPatientSearch": "Back to patient search",
79
+ "includeDeletedEncounters": "Include deleted encounters",
80
+ "cannotIncludeDeletedEncounters": "Deleted encounters can only be listed for a patient who has an identifier.",
81
+ "deletedEncountersUnavailable": "Deleted encounters unavailable",
82
+ "auditViews": "Audit views",
83
+ "searchForPatient": "Search for a patient by name or identifier",
84
+ "searchToBegin": "Enter a patient name or identifier to begin auditing their records.",
85
+ "patientSearchResults": "Patient search results",
86
+ "noPatientsFound": "No patients match \"{{searchTerm}}\".",
87
+ "patientName": "Patient name",
88
+ "gender": "Gender",
89
+ "age": "Age",
90
+ "birthdate": "Date of birth",
91
+ "backToProviderSearch": "Back to provider search",
92
+ "providerEncountersScope": "These are the encounters this provider is recorded on, which is not the same as the ones they entered or changed — the last column names who did that, and the Users tab searches by it.",
93
+ "recordedAsProvider": "Recorded as provider",
94
+ "noProviderEncountersMatch": "No encounters for this provider match these filters.",
95
+ "noProviderEncounters": "This provider is not recorded on any encounters.",
96
+ "searchForProvider": "Search for a provider by name or identifier",
97
+ "searchProviderToBegin": "Enter a provider name or identifier to see the encounters they are recorded on.",
98
+ "providerSearchResults": "Provider search results",
99
+ "noProvidersFound": "No providers match \"{{searchTerm}}\".",
100
+ "backToUserSearch": "Back to user search",
101
+ "changedBetween": "Changed between",
102
+ "readingUserObservations": "Read {{obsRead}} observations so far…",
103
+ "noUserEncountersMatch": "This user did not change any observations matching these filters.",
104
+ "noUserEncounters": "This user has not recorded or deleted any observations.",
105
+ "userScanStoppedEarly": "Stopped after reading {{obsRead}} observation changes without filling the page. Narrow the date range to search further back.",
106
+ "partialUserScan": "Part of the trail",
107
+ "lastChanged": "Last changed",
108
+ "lookingForMoreEncounters": "Looking for more encounters…",
109
+ "encounterRange": "Encounters {{from}}–{{to}}",
110
+ "searchForUser": "Search for a user by name or username",
111
+ "searchUserToBegin": "Enter a name or username to see the encounters that user has modified.",
112
+ "userSearchResults": "User search results",
113
+ "noUsersFound": "No users match \"{{searchTerm}}\".",
114
+ "name": "Name",
115
+ "username": "Username",
116
+ "retired": "Retired",
117
+ "audit": ""
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pih/esm-audit-app",
3
- "version": "2.1.1-pre.144",
3
+ "version": "2.1.1-pre.151",
4
4
  "description": "An audit trail for patient encounters and observations in the PIH EMR",
5
5
  "browser": "dist/pih-esm-audit-app.js",
6
6
  "main": "src/index.ts",
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@carbon/react';
4
+ import PatientSearch from './patient-search.component';
5
+ import ProviderSearch from './provider-search.component';
6
+ import UserSearch from './user-search.component';
7
+ import styles from './audit.scss';
8
+
9
+ export type AuditSearchTab = 'patients' | 'users' | 'providers';
10
+
11
+ export const auditSearchTabs: Array<AuditSearchTab> = ['patients', 'users', 'providers'];
12
+
13
+ interface AuditSearchProps {
14
+ tab: AuditSearchTab;
15
+ onSelectTab(tab: AuditSearchTab): void;
16
+ onSelectPatient(patientUuid: string): void;
17
+ onSelectUser(userUuid: string): void;
18
+ onSelectProvider(providerUuid: string): void;
19
+ }
20
+
21
+ /**
22
+ * Where the audit trail starts: find the record to audit — by the patient it belongs to, by the
23
+ * user who changed it, or by a provider recorded on it.
24
+ */
25
+ export default function AuditSearch({
26
+ tab,
27
+ onSelectTab,
28
+ onSelectPatient,
29
+ onSelectUser,
30
+ onSelectProvider,
31
+ }: AuditSearchProps) {
32
+ const { t } = useTranslation();
33
+
34
+ return (
35
+ <div className={styles.section}>
36
+ <Tabs
37
+ onChange={({ selectedIndex }) => onSelectTab(auditSearchTabs[selectedIndex])}
38
+ selectedIndex={Math.max(0, auditSearchTabs.indexOf(tab))}>
39
+ <TabList aria-label={t('searchBy', 'Search by')}>
40
+ <Tab>{t('patients', 'Patients')}</Tab>
41
+ <Tab>{t('users', 'Users')}</Tab>
42
+ <Tab>{t('providers', 'Providers')}</Tab>
43
+ </TabList>
44
+ <TabPanels>
45
+ <TabPanel>
46
+ <PatientSearch onSelectPatient={onSelectPatient} />
47
+ </TabPanel>
48
+ <TabPanel>
49
+ <UserSearch onSelectUser={onSelectUser} />
50
+ </TabPanel>
51
+ <TabPanel>
52
+ <ProviderSearch onSelectProvider={onSelectProvider} />
53
+ </TabPanel>
54
+ </TabPanels>
55
+ </Tabs>
56
+ </div>
57
+ );
58
+ }
@@ -2,42 +2,65 @@ import React, { useCallback } from 'react';
2
2
  import { useSearchParams } from 'react-router-dom';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { PageHeader, PageHeaderContent, PatientSearchPictogram } from '@openmrs/esm-framework';
5
+ import AuditSearch, { type AuditSearchTab, auditSearchTabs } from './audit-search.component';
5
6
  import EncounterAudit from './encounter-audit.component';
6
7
  import PatientRecord, { type PatientRecordView, patientRecordViews } from './patient-record.component';
7
- import PatientSearch from './patient-search.component';
8
+ import ProviderEncounters from './provider-encounters.component';
9
+ import UserEncounters from './user-encounters.component';
8
10
  import styles from './audit.scss';
9
11
 
12
+ interface AuditLocation {
13
+ patient?: string;
14
+ user?: string;
15
+ provider?: string;
16
+ encounter?: string;
17
+ view?: PatientRecordView;
18
+ search?: AuditSearchTab;
19
+ }
20
+
21
+ const locationParams: Array<keyof AuditLocation> = ['patient', 'user', 'provider', 'encounter', 'view', 'search'];
22
+
10
23
  /**
11
- * The audit trail is a three step drill-down — find a patient, pick one of their encounters, then
12
- * read what was entered, changed and deleted on it. The current step is held in the query string
13
- * so that a particular encounter's audit trail can be linked to and so that the browser's back
14
- * button walks back up the drill-down.
24
+ * The audit trail is a drill-down — find the record, pick one of its encounters, then read what
25
+ * was entered, changed and deleted on it. Where it is drilled down to is held in the query
26
+ * string, so a particular encounter's audit trail can be linked to and so that the browser's back
27
+ * button walks back up.
15
28
  */
16
29
  export default function Audit() {
17
30
  const { t } = useTranslation();
18
31
  const [searchParams, setSearchParams] = useSearchParams();
19
32
  const patientUuid = searchParams.get('patient');
33
+ const userUuid = searchParams.get('user');
34
+ const providerUuid = searchParams.get('provider');
20
35
  const encounterUuid = searchParams.get('encounter');
21
- const view = searchParams.get('view');
22
- const patientRecordView: PatientRecordView = patientRecordViews.includes(view as PatientRecordView)
23
- ? (view as PatientRecordView)
24
- : 'encounters';
36
+
37
+ const oneOf = <T,>(allowed: Array<T>, value: string | null, fallback: T): T =>
38
+ allowed.includes(value as T) ? (value as T) : fallback;
39
+ const patientRecordView = oneOf(patientRecordViews, searchParams.get('view'), 'encounters');
40
+ const searchTab = oneOf(auditSearchTabs, searchParams.get('search'), 'patients');
25
41
 
26
42
  const goTo = useCallback(
27
- (next: { patient?: string; encounter?: string; view?: PatientRecordView }) => {
43
+ (next: AuditLocation) => {
28
44
  const params = new URLSearchParams(searchParams);
29
- params.delete('patient');
30
- params.delete('encounter');
31
- params.delete('view');
45
+ locationParams.forEach((param) => params.delete(param));
32
46
  if (next.patient) {
33
47
  params.set('patient', next.patient);
34
48
  }
49
+ if (next.user) {
50
+ params.set('user', next.user);
51
+ }
52
+ if (next.provider) {
53
+ params.set('provider', next.provider);
54
+ }
35
55
  if (next.encounter) {
36
56
  params.set('encounter', next.encounter);
37
57
  }
38
58
  if (next.view && next.view !== 'encounters') {
39
59
  params.set('view', next.view);
40
60
  }
61
+ if (next.search && next.search !== 'patients') {
62
+ params.set('search', next.search);
63
+ }
41
64
  setSearchParams(params);
42
65
  },
43
66
  [searchParams, setSearchParams],
@@ -51,18 +74,42 @@ export default function Audit() {
51
74
  {encounterUuid ? (
52
75
  <EncounterAudit
53
76
  encounterUuid={encounterUuid}
54
- onBackToEncounters={(uuid) => goTo({ patient: uuid ?? patientUuid, view: patientRecordView })}
77
+ onBackToEncounters={(uuid) =>
78
+ userUuid
79
+ ? goTo({ user: userUuid })
80
+ : providerUuid
81
+ ? goTo({ provider: providerUuid })
82
+ : goTo({ patient: uuid ?? patientUuid, view: patientRecordView })
83
+ }
84
+ />
85
+ ) : userUuid ? (
86
+ <UserEncounters
87
+ onBackToSearch={() => goTo({ search: 'users' })}
88
+ onSelectEncounter={(uuid) => goTo({ user: userUuid, encounter: uuid })}
89
+ userUuid={userUuid}
90
+ />
91
+ ) : providerUuid ? (
92
+ <ProviderEncounters
93
+ onBackToSearch={() => goTo({ search: 'providers' })}
94
+ onSelectEncounter={(uuid) => goTo({ provider: providerUuid, encounter: uuid })}
95
+ providerUuid={providerUuid}
55
96
  />
56
97
  ) : patientUuid ? (
57
98
  <PatientRecord
58
- onBackToSearch={() => goTo({})}
99
+ onBackToSearch={() => goTo({ search: 'patients' })}
59
100
  onSelectEncounter={(uuid) => goTo({ patient: patientUuid, encounter: uuid, view: patientRecordView })}
60
101
  onSelectView={(nextView) => goTo({ patient: patientUuid, view: nextView })}
61
102
  patientUuid={patientUuid}
62
103
  view={patientRecordView}
63
104
  />
64
105
  ) : (
65
- <PatientSearch onSelectPatient={(uuid) => goTo({ patient: uuid })} />
106
+ <AuditSearch
107
+ onSelectPatient={(uuid) => goTo({ patient: uuid })}
108
+ onSelectProvider={(uuid) => goTo({ provider: uuid })}
109
+ onSelectTab={(tab) => goTo({ search: tab })}
110
+ onSelectUser={(uuid) => goTo({ user: uuid })}
111
+ tab={searchTab}
112
+ />
66
113
  )}
67
114
  </div>
68
115
  );