@kenyaemr/esm-ward-app 7.0.3-pre.88 → 7.0.3-pre.94

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 (146) hide show
  1. package/.turbo/turbo-build.log +24 -16
  2. package/dist/130.js +1 -1
  3. package/dist/130.js.map +1 -1
  4. package/dist/169.js +1 -0
  5. package/dist/169.js.map +1 -0
  6. package/dist/269.js +1 -0
  7. package/dist/269.js.map +1 -0
  8. package/dist/346.js +1 -0
  9. package/dist/346.js.map +1 -0
  10. package/dist/348.js +1 -0
  11. package/dist/348.js.map +1 -0
  12. package/dist/466.js +1 -0
  13. package/dist/466.js.map +1 -0
  14. package/dist/501.js +1 -0
  15. package/dist/501.js.map +1 -0
  16. package/dist/574.js +1 -1
  17. package/dist/577.js +1 -0
  18. package/dist/577.js.map +1 -0
  19. package/dist/659.js +1 -0
  20. package/dist/659.js.map +1 -0
  21. package/dist/749.js +1 -0
  22. package/dist/749.js.map +1 -0
  23. package/dist/76.js +1 -0
  24. package/dist/76.js.map +1 -0
  25. package/dist/767.js +1 -0
  26. package/dist/767.js.map +1 -0
  27. package/dist/793.js +2 -0
  28. package/dist/793.js.map +1 -0
  29. package/dist/803.js +1 -0
  30. package/dist/803.js.map +1 -0
  31. package/dist/940.js +1 -0
  32. package/dist/940.js.map +1 -0
  33. package/dist/960.js +1 -0
  34. package/dist/960.js.map +1 -0
  35. package/dist/kenyaemr-esm-ward-app.js +1 -1
  36. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +330 -42
  37. package/dist/kenyaemr-esm-ward-app.js.map +1 -1
  38. package/dist/main.js +1 -1
  39. package/dist/main.js.map +1 -1
  40. package/dist/routes.json +1 -1
  41. package/package.json +2 -2
  42. package/src/action-menu-buttons/transfer-workspace-siderail.component.tsx +27 -0
  43. package/src/beds/empty-bed.component.tsx +1 -1
  44. package/src/beds/empty-bed.scss +6 -6
  45. package/src/beds/occupied-bed.component.tsx +5 -5
  46. package/src/beds/occupied-bed.scss +2 -3
  47. package/src/beds/occupied-bed.test.tsx +37 -21
  48. package/src/beds/unassigned-patient.component.tsx +20 -0
  49. package/src/beds/unassigned-patient.scss +6 -0
  50. package/src/config-schema-admission-request-note.ts +9 -0
  51. package/src/config-schema-extension-colored-obs-tags.ts +91 -0
  52. package/src/config-schema.ts +165 -231
  53. package/src/createDashboardLink.component.tsx +42 -0
  54. package/src/hooks/useAdmissionLocation.ts +12 -7
  55. package/src/hooks/useCurrentWardCardConfig.ts +32 -0
  56. package/src/hooks/useEmrConfiguration.ts +112 -0
  57. package/src/hooks/useInpatientAdmission.ts +28 -0
  58. package/src/hooks/useInpatientRequest.ts +39 -9
  59. package/src/hooks/useLocation.test.ts +38 -0
  60. package/src/hooks/useLocation.ts +9 -0
  61. package/src/hooks/useLocations.ts +54 -0
  62. package/src/hooks/useMostRecentObs.ts +27 -0
  63. package/src/hooks/useRestPatient.ts +18 -0
  64. package/src/hooks/useWardLocation.test.ts +108 -0
  65. package/src/hooks/useWardLocation.ts +26 -0
  66. package/src/index.ts +71 -4
  67. package/src/location-selector/location-selector.component.tsx +118 -0
  68. package/src/location-selector/location-selector.scss +48 -0
  69. package/src/root.component.tsx +2 -1
  70. package/src/routes.json +79 -12
  71. package/src/types/index.ts +87 -46
  72. package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +27 -0
  73. package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +13 -0
  74. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +7 -13
  75. package/src/ward-patient-card/row-elements/ward-patient-bed-number.tsx +2 -2
  76. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +51 -50
  77. package/src/ward-patient-card/row-elements/ward-patient-gender.component.tsx +27 -0
  78. package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +16 -15
  79. package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +53 -0
  80. package/src/ward-patient-card/row-elements/ward-patient-name.tsx +7 -7
  81. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +4 -4
  82. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +45 -44
  83. package/src/ward-patient-card/row-elements/ward-patient-time-on-ward.tsx +22 -0
  84. package/src/ward-patient-card/row-elements/ward-patient-time-since-admission.tsx +22 -0
  85. package/src/ward-patient-card/ward-patient-card-element.component.tsx +65 -0
  86. package/src/ward-patient-card/ward-patient-card.component.tsx +64 -0
  87. package/src/ward-patient-card/ward-patient-card.scss +61 -12
  88. package/src/ward-patient-workspace/ward-patient-action-button.extension.tsx +18 -0
  89. package/src/ward-patient-workspace/ward-patient.style.scss +11 -0
  90. package/src/ward-patient-workspace/ward-patient.workspace.tsx +51 -0
  91. package/src/ward-view/ward-bed.component.tsx +0 -1
  92. package/src/ward-view/ward-view.component.tsx +114 -76
  93. package/src/ward-view/ward-view.resource.ts +2 -2
  94. package/src/ward-view/ward-view.scss +4 -4
  95. package/src/ward-view/ward-view.test.tsx +76 -49
  96. package/src/ward-view-header/admission-requests-bar.component.tsx +29 -28
  97. package/src/ward-view-header/admission-requests-bar.test.tsx +11 -15
  98. package/src/ward-view-header/admission-requests.scss +20 -25
  99. package/src/ward-view-header/ward-view-header.component.tsx +7 -7
  100. package/src/ward-view-header/ward-view-header.scss +2 -2
  101. package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +29 -0
  102. package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +51 -0
  103. package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +16 -0
  104. package/src/ward-workspace/admission-request-card/admission-request-card.scss +49 -0
  105. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.scss +12 -0
  106. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +48 -0
  107. package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +61 -0
  108. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.scss +35 -0
  109. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +341 -0
  110. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +267 -0
  111. package/src/ward-workspace/admit-patient-form-workspace/types.ts +7 -0
  112. package/src/ward-workspace/patient-banner/patient-banner.component.tsx +29 -0
  113. package/src/ward-workspace/patient-banner/style.scss +23 -0
  114. package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +210 -0
  115. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +238 -0
  116. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +73 -0
  117. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +44 -0
  118. package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +180 -0
  119. package/src/ward-workspace/ward-patient-notes/form/notes-form.scss +30 -0
  120. package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +116 -0
  121. package/src/ward-workspace/ward-patient-notes/history/note.component.tsx +53 -0
  122. package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +55 -0
  123. package/src/ward-workspace/ward-patient-notes/history/notes-container.test.tsx +84 -0
  124. package/src/ward-workspace/ward-patient-notes/history/styles.scss +61 -0
  125. package/src/ward-workspace/ward-patient-notes/notes-action-button.extension.tsx +18 -0
  126. package/src/ward-workspace/ward-patient-notes/notes.resource.ts +71 -0
  127. package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +25 -0
  128. package/src/ward-workspace/ward-patient-notes/types.ts +44 -0
  129. package/src/ward.resource.ts +25 -0
  130. package/translations/en.json +63 -2
  131. package/dist/443.js +0 -1
  132. package/dist/443.js.map +0 -1
  133. package/dist/589.js +0 -1
  134. package/dist/589.js.map +0 -1
  135. package/dist/695.js +0 -2
  136. package/dist/695.js.map +0 -1
  137. package/src/hooks/useAdmittedPatients.ts +0 -13
  138. package/src/ward-patient-card/row-elements/row-elements.scss +0 -16
  139. package/src/ward-patient-card/ward-patient-card-row.resources.tsx +0 -92
  140. package/src/ward-patient-card/ward-patient-card.tsx +0 -20
  141. package/src/ward-workspace/admission-request-card.component.tsx +0 -23
  142. package/src/ward-workspace/admission-request-card.scss +0 -34
  143. package/src/ward-workspace/admission-request-workspace.test.tsx +0 -38
  144. package/src/ward-workspace/admission-requests-workspace.component.tsx +0 -21
  145. package/src/ward-workspace/admission-requests-workspace.scss +0 -13
  146. /package/dist/{695.js.LICENSE.txt → 793.js.LICENSE.txt} +0 -0
package/src/routes.json CHANGED
@@ -4,28 +4,95 @@
4
4
  "webservices.rest": "^2.2.0",
5
5
  "emrapi": "^2.0.0 || 2.0.0-SNAPSHOT"
6
6
  },
7
- "optionalBackendDependencies":{
8
- "bedmanagement":{
7
+ "optionalBackendDependencies": {
8
+ "bedmanagement": {
9
9
  "version": "^6.0.0 || 6.0.0-SNAPSHOT",
10
10
  "feature": {
11
11
  "flagName": "bedmanagement-module",
12
- "label":"Ward App Patient Service",
12
+ "label": "Ward App Patient Service",
13
13
  "description": "This module, if installed, provides services for managing patients admitted to the ward."
14
14
  }
15
- }
15
+ }
16
16
  },
17
- "workspaces": [
17
+ "extensions": [
18
18
  {
19
- "name":"admission-requests-cards",
20
- "component": "admissionRequestWorkspace",
21
- "title":"admissionRequests",
22
- "type":"admission-requests"
19
+ "name": "ward-dashboard-link",
20
+ "component": "wardDashboardLink",
21
+ "slot": "homepage-dashboard-slot",
22
+ "meta": {
23
+ "name": "ward",
24
+ "slot": "ward-dashboard-slot",
25
+ "title": "Wards"
26
+ }
27
+ },
28
+ {
29
+ "component": "root",
30
+ "name": "ward-dashboard",
31
+ "slot": "ward-dashboard-slot"
32
+ },
33
+ {
34
+ "component": "wardPatientActionButtonExtension",
35
+ "name": "ward-patient-action-button",
36
+ "slot": "action-menu-ward-patient-items-slot"
37
+ },
38
+ {
39
+ "component": "wardPatientNotesActionButtonExtension",
40
+ "name": "ward-inpatient-notes-form-action-button",
41
+ "slot": "action-menu-ward-patient-items-slot"
42
+ },
43
+ {
44
+ "component": "coloredObsTagCardRowExtension",
45
+ "name": "colored-obs-tags-card-row",
46
+ "slot": "ward-patient-card-slot"
47
+ },
48
+ {
49
+ "name": "transfer-swap-patient-siderail-button",
50
+ "slot": "action-menu-ward-patient-items-slot",
51
+ "component": "patientTransferAndSwapWorkspaceSiderailIcon"
52
+ },
53
+ {
54
+ "component": "admissionRequestNoteRowExtension",
55
+ "name": "admission-request-note-card-row",
56
+ "slot": "ward-patient-card-slot"
23
57
  }
24
58
  ],
25
- "pages": [
59
+ "workspaces": [
26
60
  {
27
- "component": "root",
28
- "route": "ward"
61
+ "name": "admission-requests-workspace",
62
+ "component": "admissionRequestWorkspace",
63
+ "title": "admissionRequests",
64
+ "type": "admission-requests"
65
+ },
66
+ {
67
+ "name": "ward-patient-notes-workspace",
68
+ "component": "wardPatientNotesWorkspace",
69
+ "type": "ward-patient-notes",
70
+ "title": "inpatientNotesWorkspaceTitle",
71
+ "sidebarFamily": "ward-patient",
72
+ "hasOwnSidebar": true
73
+ },
74
+ {
75
+ "name": "admit-patient-form-workspace",
76
+ "component": "admitPatientFormWorkspace",
77
+ "title": "admissionRequests",
78
+ "type": "admission-requests"
79
+ },
80
+ {
81
+ "name": "ward-patient-workspace",
82
+ "component": "wardPatientWorkspace",
83
+ "type": "ward",
84
+ "title": "Ward Patient",
85
+ "width": "extra-wide",
86
+ "hasOwnSidebar": true,
87
+ "sidebarFamily": "ward-patient"
88
+ },
89
+ {
90
+ "name": "patient-transfer-swap-workspace",
91
+ "component": "patientTransferAndSwapWorkspace",
92
+ "title": "transfers",
93
+ "type": "transfer-swap-bed-form",
94
+ "hasOwnSidebar": true,
95
+ "sidebarFamily": "ward-patient"
29
96
  }
30
97
  ]
31
98
  }
@@ -1,45 +1,59 @@
1
- import {
2
- type OpenmrsResource,
3
- type OpenmrsResourceStrict,
4
- type Person,
5
- type Visit,
6
- type Location,
7
- type Patient,
1
+ import type {
2
+ Concept,
3
+ DefaultWorkspaceProps,
4
+ Location,
5
+ OpenmrsResource,
6
+ OpenmrsResourceStrict,
7
+ Patient,
8
+ Person,
9
+ Visit,
8
10
  } from '@openmrs/esm-framework';
9
11
  import type React from 'react';
10
12
 
11
- export interface WardPatientCardProps {
13
+ export type WardPatientCard = React.FC<WardPatient>;
14
+
15
+ // WardPatient is a patient admitted to a ward, and/or in a bed on a ward
16
+ export type WardPatient = {
17
+ /**
18
+ * The patient and their current visit. These values are taken either
19
+ * from either the inpatientAdmission object, the inpatientRequest object
20
+ * or the admissionLocation object (which contains the bed)
21
+ */
12
22
  patient: Patient;
13
23
  visit: Visit;
14
- bed: Bed;
15
- }
16
24
 
17
- export type WardPatientCardRow = React.FC<WardPatientCardProps>;
18
- export type WardPatientCardElement = React.FC<WardPatientCardProps>;
19
-
20
- export const patientCardElementTypes = [
21
- 'bed-number',
22
- 'patient-name',
23
- 'patient-age',
24
- 'patient-address',
25
- 'patient-obs',
26
- 'patient-coded-obs-tags',
27
- 'admission-time',
28
- ] as const;
29
- export type PatientCardElementType = (typeof patientCardElementTypes)[number];
25
+ /**
26
+ * the bed assigned to the patient. This object is only set if the patient
27
+ * has a bed assigned
28
+ */
29
+ bed: Bed;
30
30
 
31
- // a Ward Patient can either be a patient that is already admitted or a
32
- // patient that is awaiting admission
33
- export type WardPatient = (AdmittedPatient & { admitted: true }) | (InpatientRequest & { admitted: false });
31
+ /**
32
+ * The admission detail. This object is only set if the patient has been
33
+ * admitted to the ward
34
+ */
35
+ inpatientAdmission: InpatientAdmission;
36
+
37
+ /**
38
+ * The admission request. The object is only set if the patient has a
39
+ * pending admission / transfer request.
40
+ */
41
+ inpatientRequest: InpatientRequest;
42
+ };
43
+ export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps {
44
+ wardPatient: WardPatient;
45
+ }
34
46
 
35
47
  // server-side types defined in openmrs-module-bedmanagement:
36
48
 
37
- export interface AdmissionLocation {
49
+ // note "AdmissionLocationResponse" isn't the clearest name, but it matches the endpoint; endpoint fetches bed information (including info about patients in those beds) for a location (as provided by the bed management module)
50
+ export interface AdmissionLocationFetchResponse {
38
51
  totalBeds: number;
39
52
  occupiedBeds: number;
40
53
  ward: Location;
41
54
  bedLayouts: Array<BedLayout>;
42
55
  }
56
+
43
57
  export interface Bed {
44
58
  id: number;
45
59
  uuid: string;
@@ -83,34 +97,43 @@ interface BedTagMap {
83
97
 
84
98
  export type BedStatus = 'AVAILABLE' | 'OCCUPIED';
85
99
 
86
- // server-side types defined in openmrs-module-emrapi:
87
-
88
- export type DispositionType = 'ADMISSION' | 'TRANSFER' | 'DISCHARGE';
100
+ // GET /rest/emrapi/inpatient/request
101
+ export interface InpatientRequestFetchResponse {
102
+ results: InpatientRequest[];
103
+ }
89
104
 
90
- // InpatientRequest[] returned by:
91
- // GET /rest/emrapi/inpatient/admissionRequests
92
- // GET /rest/emrapi/inpatient/transferRequests
93
- // GET /rest/emrapi/inpatient/admissionAndTransferRequests
94
105
  export interface InpatientRequest {
95
106
  patient: Patient;
96
- visit: Visit;
97
- type: DispositionType;
98
-
99
- // as of now, these fields are not included in the backend
100
- encounter?: Encounter;
101
- dispositionObs?: Observation;
107
+ dispositionType: DispositionType;
108
+ disposition: Concept;
109
+ dispositionEncounter?: Encounter;
110
+ dispositionObsGroup?: Observation;
102
111
  dispositionLocation?: Location;
103
- dispositionDate?: Date;
112
+ visit: Visit;
104
113
  }
105
114
 
106
- // AdmittedPatient[] returned by:
115
+ export type DispositionType = 'ADMIT' | 'TRANSFER' | 'DISCHARGE';
116
+
107
117
  // GET /rest/emrapi/inpatient/visits
108
- export interface AdmittedPatient {
118
+ export interface InpatientAdmissionFetchResponse {
119
+ results: InpatientAdmission[];
120
+ }
121
+
122
+ export interface InpatientAdmission {
109
123
  patient: Patient;
110
124
  visit: Visit;
111
- currentLocation: Location;
112
- timeSinceAdmissionInMinutes: number;
113
- timeAtInpatientLocationInMinutes: number;
125
+
126
+ // the encounter of type "Admission" or "Transfer" that is responsible
127
+ // for assigning the patient to the current inpatient location. For example,
128
+ // if the patient has been admitted /transferred to multiple locations as follows:
129
+ // A -> B -> A
130
+ // then encounterAssigningToCurrentInpatientLocation
131
+ // would be the transfer encounter that lands the patient back to A.
132
+ encounterAssigningToCurrentInpatientLocation: Encounter;
133
+
134
+ // the first encounter of the visit that is of encounterType "Admission" or "Transfer",
135
+ // regardless of the admission location
136
+ firstAdmissionOrTransferEncounter: Encounter;
114
137
  }
115
138
 
116
139
  // TODO: Move these types to esm-core
@@ -161,3 +184,21 @@ export interface EncounterRole extends OpenmrsResourceStrict {
161
184
  description?: string;
162
185
  retired?: boolean;
163
186
  }
187
+
188
+ export interface EncounterPayload {
189
+ encounterDatetime?: string;
190
+ encounterType: string;
191
+ patient: string;
192
+ location: string;
193
+ encounterProviders?: Array<{ encounterRole: string; provider: string }>;
194
+ obs: Array<ObsPayload>;
195
+ form?: string;
196
+ orders?: Array<any>;
197
+ visit?: string;
198
+ }
199
+
200
+ export interface ObsPayload {
201
+ concept: Concept | string;
202
+ value?: string;
203
+ groupMembers?: Array<ObsPayload>;
204
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { type ObsElementDefinition } from '../../config-schema';
3
+ import { type WardPatientCard } from '../../types';
4
+ import WardPatientObs from '../row-elements/ward-patient-obs';
5
+ import { useConfig } from '@openmrs/esm-framework';
6
+
7
+ const AdmissionRequestNoteRowExtension: WardPatientCard = ({ patient, visit, inpatientAdmission }) => {
8
+ const { conceptUuid } = useConfig<ObsElementDefinition>();
9
+ const config: ObsElementDefinition = {
10
+ conceptUuid,
11
+ limit: 0,
12
+ id: 'admission-note',
13
+ onlyWithinCurrentVisit: true,
14
+ orderBy: 'ascending',
15
+ label: 'Admission Note',
16
+ };
17
+
18
+ // only show if the patient has not been admitted yet
19
+ const admitted = inpatientAdmission != null;
20
+ if (admitted) {
21
+ return <></>;
22
+ } else {
23
+ return <WardPatientObs config={config} patient={patient} visit={visit} />;
24
+ }
25
+ };
26
+
27
+ export default AdmissionRequestNoteRowExtension;
@@ -0,0 +1,13 @@
1
+ import { useConfig } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags';
4
+ import { type WardPatientCard } from '../../types';
5
+ import WardPatientCodedObsTags from '../row-elements/ward-patient-coded-obs-tags';
6
+
7
+ const ColoredObsTagsCardRowExtension: WardPatientCard = ({ patient, visit }) => {
8
+ const config = useConfig<ColoredObsTagsCardRowConfigObject>();
9
+
10
+ return <WardPatientCodedObsTags config={config} patient={patient} visit={visit} />;
11
+ };
12
+
13
+ export default ColoredObsTagsCardRowExtension;
@@ -1,18 +1,12 @@
1
+ import { age, type Patient } from '@openmrs/esm-framework';
1
2
  import React from 'react';
2
- import { useTranslation } from 'react-i18next';
3
- import { type WardPatientCardElement } from '../../types';
4
3
 
5
- const WardPatientAge: WardPatientCardElement = ({ patient }) => {
6
- const { t } = useTranslation();
7
- // TODO: BED-10
8
- // make the backend return patient.person.birthdate so we can use the age() function to calculate age
9
- return (
10
- <div>
11
- {t('yearsOld', '{{age}} yrs', {
12
- age: patient?.person?.age,
13
- })}
14
- </div>
15
- );
4
+ export interface WardPatientAgeProps {
5
+ patient: Patient;
6
+ }
7
+
8
+ const WardPatientAge: React.FC<WardPatientAgeProps> = ({ patient }) => {
9
+ return <div>{age(patient.person?.birthdate)}</div>;
16
10
  };
17
11
 
18
12
  export default WardPatientAge;
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
+ import { type Bed } from '../../types';
2
3
  import styles from '../ward-patient-card.scss';
3
- import { type WardPatientCardElement } from '../../types';
4
4
 
5
- const WardPatientBedNumber: WardPatientCardElement = ({ bed }) => {
5
+ const WardPatientBedNumber: React.FC<{ bed: Bed }> = ({ bed }) => {
6
6
  if (!bed) {
7
7
  return <></>;
8
8
  }
@@ -1,13 +1,18 @@
1
1
  import { SkeletonText, Tag } from '@carbon/react';
2
- import { translateFrom, type OpenmrsResource } from '@openmrs/esm-framework';
2
+ import { type Patient, translateFrom, type Visit, type OpenmrsResource } from '@openmrs/esm-framework';
3
3
  import React from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { type PatientCodedObsTagsElementConfig } from '../../config-schema';
6
5
  import { moduleName } from '../../constant';
7
6
  import { useObs } from '../../hooks/useObs';
8
- import { type WardPatientCardElement } from '../../types';
9
7
  import styles from '../ward-patient-card.scss';
10
8
  import { obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource';
9
+ import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags';
10
+
11
+ interface WardPatientCodedObsTagsProps {
12
+ config: ColoredObsTagsCardRowConfigObject;
13
+ patient: Patient;
14
+ visit: Visit;
15
+ }
11
16
 
12
17
  /**
13
18
  * The WardPatientCodedObsTags displays observations of coded values of a particular concept in the active visit as tags.
@@ -19,62 +24,58 @@ import { obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient
19
24
  * @param config
20
25
  * @returns
21
26
  */
22
- const wardPatientCodedObsTags = (config: PatientCodedObsTagsElementConfig) => {
23
- const WardPatientCodedObsTags: WardPatientCardElement = ({ patient, visit }) => {
24
- const { conceptUuid, summaryLabel, summaryLabelColor, summaryLabelI18nModule } = config;
25
- const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation);
26
- const { t } = useTranslation();
27
- const { data: conceptToTagColorMap } = useConceptToTagColorMap(config);
28
-
29
- if (isLoading) {
30
- return <SkeletonText />;
31
- } else {
32
- const obsToDisplay = data?.data?.results?.filter((o) => {
33
- const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
34
- return matchVisit || visit == null; // TODO: remove visit == null hack when server API supports returning visit
35
- });
27
+ const WardPatientCodedObsTags: React.FC<WardPatientCodedObsTagsProps> = ({ config, patient, visit }) => {
28
+ const { conceptUuid, summaryLabel, summaryLabelColor, summaryLabelI18nModule } = config;
29
+ const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation);
30
+ const { t } = useTranslation();
31
+ const { data: conceptToTagColorMap } = useConceptToTagColorMap(config.tags);
36
32
 
37
- const summaryLabelToDisplay =
38
- summaryLabel != null
39
- ? translateFrom(summaryLabelI18nModule ?? moduleName, summaryLabel)
40
- : obsToDisplay?.[0]?.concept?.display;
33
+ if (isLoading) {
34
+ return <SkeletonText />;
35
+ } else {
36
+ const obsToDisplay = data?.data?.results?.filter((o) => {
37
+ const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
38
+ return matchVisit || visit == null; // TODO: remove visit == null hack when server API supports returning visit
39
+ });
41
40
 
42
- const obsNodes = obsToDisplay?.map((o) => {
43
- const { display, uuid } = o.value as OpenmrsResource;
41
+ const summaryLabelToDisplay =
42
+ summaryLabel != null
43
+ ? translateFrom(summaryLabelI18nModule ?? moduleName, summaryLabel)
44
+ : obsToDisplay?.[0]?.concept?.display;
44
45
 
45
- const color = conceptToTagColorMap?.get(uuid);
46
- if (color) {
47
- return (
48
- <Tag type={color} key={uuid}>
49
- {display}
50
- </Tag>
51
- );
52
- } else {
53
- return null;
54
- }
55
- });
46
+ const obsNodes = obsToDisplay?.map((o) => {
47
+ const { display, uuid } = o.value as OpenmrsResource;
56
48
 
57
- const obsWithNoTagCount = obsNodes.filter((o) => o == null).length;
58
- if (obsNodes?.length > 0 || obsWithNoTagCount > 0) {
49
+ const color = conceptToTagColorMap?.get(uuid);
50
+ if (color) {
59
51
  return (
60
- <div>
61
- <span className={styles.wardPatientObsLabel}>
62
- {obsNodes}
63
- {obsWithNoTagCount > 0 ? (
64
- <Tag type={summaryLabelColor}>
65
- {t('countItems', '{{count}} {{item}}', { count: obsWithNoTagCount, item: summaryLabelToDisplay })}
66
- </Tag>
67
- ) : null}
68
- </span>
69
- </div>
52
+ <Tag type={color} key={`ward-coded-obs-tag-${o.uuid}`}>
53
+ {display}
54
+ </Tag>
70
55
  );
71
56
  } else {
72
57
  return null;
73
58
  }
74
- }
75
- };
59
+ });
76
60
 
77
- return WardPatientCodedObsTags;
61
+ const obsWithNoTagCount = obsNodes.filter((o) => o == null).length;
62
+ if (obsNodes?.length > 0 || obsWithNoTagCount > 0) {
63
+ return (
64
+ <div>
65
+ <span className={styles.wardPatientObsLabel}>
66
+ {obsNodes}
67
+ {obsWithNoTagCount > 0 ? (
68
+ <Tag type={summaryLabelColor}>
69
+ {t('countItems', '{{count}} {{item}}', { count: obsWithNoTagCount, item: summaryLabelToDisplay })}
70
+ </Tag>
71
+ ) : null}
72
+ </span>
73
+ </div>
74
+ );
75
+ } else {
76
+ return null;
77
+ }
78
+ }
78
79
  };
79
80
 
80
- export default wardPatientCodedObsTags;
81
+ export default WardPatientCodedObsTags;
@@ -0,0 +1,27 @@
1
+ import { type Patient } from '@openmrs/esm-framework';
2
+ import { type TFunction } from 'i18next';
3
+ import React from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ const WardPatientGender: React.FC<{ patient: Patient }> = ({ patient }) => {
7
+ const { t } = useTranslation();
8
+
9
+ return <div>{getGender(t, patient?.person?.gender)}</div>;
10
+ };
11
+
12
+ export const getGender = (t: TFunction, gender: string): string => {
13
+ switch (gender) {
14
+ case 'M':
15
+ return t('male', 'Male');
16
+ case 'F':
17
+ return t('female', 'Female');
18
+ case 'O':
19
+ return t('other', 'Other');
20
+ case 'unknown':
21
+ return t('unknown', 'Unknown');
22
+ default:
23
+ return gender;
24
+ }
25
+ };
26
+
27
+ export default WardPatientGender;
@@ -1,22 +1,23 @@
1
1
  import React from 'react';
2
- import { type WardPatientCardElement } from '../../types';
3
2
  import styles from '../ward-patient-card.scss';
4
- import { type PatientAddressElementConfig } from '../../config-schema';
3
+ import { type Patient } from '@openmrs/esm-framework';
4
+ import { type AddressElementDefinition } from '../../config-schema';
5
5
 
6
- const wardPatientAddress = (config: PatientAddressElementConfig) => {
7
- const wardPatientAddress: WardPatientCardElement = ({ patient }) => {
8
- const { addressFields } = config;
6
+ export interface WardPatientAddressProps {
7
+ patient: Patient;
8
+ config: AddressElementDefinition;
9
+ }
9
10
 
10
- const preferredAddress = patient?.person?.preferredAddress;
11
+ const WardPatientAddress: React.FC<WardPatientAddressProps> = ({ patient, config }) => {
12
+ const preferredAddress = patient?.person?.preferredAddress;
11
13
 
12
- return (
13
- <div className={styles.wardPatientAddress}>
14
- {addressFields?.map((field, i) => <div key={i}>{preferredAddress?.[field] as string}</div>)}
15
- </div>
16
- );
17
- };
18
-
19
- return wardPatientAddress;
14
+ return (
15
+ <>
16
+ {config.fields?.map((field, i) =>
17
+ preferredAddress?.[field] ? <div key={i}>{preferredAddress?.[field] as string}</div> : <div key={i}></div>,
18
+ )}
19
+ </>
20
+ );
20
21
  };
21
22
 
22
- export default wardPatientAddress;
23
+ export default WardPatientAddress;
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { type IdentifierElementDefinition } from '../../config-schema';
3
+ import { Tag } from '@carbon/react';
4
+ import { type Patient, translateFrom, type PatientIdentifier } from '@openmrs/esm-framework';
5
+ import { moduleName } from '../../constant';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ /** Sort the identifiers by preferred first. The identifier with value of true
9
+ * takes precedence over false. If both identifiers have same preferred value,
10
+ * sort them by most recently created or changed. */
11
+ const identifierCompareFunction = (pi1: PatientIdentifier, pi2: PatientIdentifier) => {
12
+ let comp = (pi2.preferred ? 1 : 0) - (pi1.preferred ? 1 : 0);
13
+
14
+ if (comp == 0) {
15
+ const date1 = pi1.auditInfo.dateChanged ?? pi1.auditInfo.dateCreated;
16
+ const date2 = pi2.auditInfo.dateChanged ?? pi2.auditInfo.dateCreated;
17
+ comp = date2.localeCompare(date1);
18
+ }
19
+ return comp;
20
+ };
21
+
22
+ export interface WardPatientIdentifierProps {
23
+ patient: Patient;
24
+ /** If the config is not passed, this will be the default identifier element, which uses the preferred identifier type. */
25
+ config?: IdentifierElementDefinition;
26
+ }
27
+
28
+ const defaultConfig: IdentifierElementDefinition = {
29
+ id: 'patient-identifier',
30
+ identifierTypeUuid: null,
31
+ };
32
+
33
+ const WardPatientIdentifier: React.FC<WardPatientIdentifierProps> = ({ config: configProp, patient }) => {
34
+ const { t } = useTranslation();
35
+ const config = configProp ?? defaultConfig;
36
+ const { identifierTypeUuid, labelI18nModule: labelModule, label } = config;
37
+ const patientIdentifiers = patient.identifiers.filter(
38
+ (patientIdentifier: PatientIdentifier) =>
39
+ identifierTypeUuid == null || patientIdentifier.identifierType?.uuid === identifierTypeUuid,
40
+ );
41
+ patientIdentifiers.sort(identifierCompareFunction);
42
+ const patientIdentifier = patientIdentifiers[0];
43
+ const labelToDisplay =
44
+ label != null ? translateFrom(labelModule ?? moduleName, label) : patientIdentifier?.identifierType?.name;
45
+ return (
46
+ <div>
47
+ {labelToDisplay ? <Tag>{t('identifierTypelabel', '{{label}}:', { label: labelToDisplay })}</Tag> : <></>}
48
+ <span>{patientIdentifier?.identifier}</span>
49
+ </div>
50
+ );
51
+ };
52
+
53
+ export default WardPatientIdentifier;
@@ -1,13 +1,13 @@
1
1
  import React from 'react';
2
- import { type WardPatientCardElement } from '../../types';
2
+ import { type Patient } from '@openmrs/esm-framework';
3
3
  import styles from '../ward-patient-card.scss';
4
4
 
5
- const WardPatientName: WardPatientCardElement = ({ patient }) => {
6
- // TODO: BED-10
7
- // make server return patient.display and use that instead
8
- const { givenName, familyName } = patient?.person?.preferredName;
9
- const name = `${givenName} ${familyName}`;
10
- return <div className={styles.wardPatientName}>{name}</div>;
5
+ export interface WardPatientNameProps {
6
+ patient: Patient;
7
+ }
8
+
9
+ const WardPatientName: React.FC<WardPatientNameProps> = ({ patient }) => {
10
+ return <div className={styles.wardPatientName}>{patient?.person?.preferredName?.display}</div>;
11
11
  };
12
12
 
13
13
  export default WardPatientName;
@@ -1,6 +1,6 @@
1
1
  import { openmrsFetch, restBaseUrl, type Concept } from '@openmrs/esm-framework';
2
2
  import useSWRImmutable from 'swr/immutable';
3
- import { type PatientCodedObsTagsElementConfig } from '../../config-schema';
3
+ import { type TagConfigObject } from '../../config-schema-extension-colored-obs-tags';
4
4
 
5
5
  // prettier-ignore
6
6
  export const obsCustomRepresentation =
@@ -12,13 +12,13 @@ export const obsCustomRepresentation =
12
12
  // get the setMembers of a concept set
13
13
  const conceptSetCustomRepresentation = 'custom:(uuid,setMembers:(uuid))';
14
14
 
15
- export function useConceptToTagColorMap(codedObsTagsConfig: PatientCodedObsTagsElementConfig) {
15
+ export function useConceptToTagColorMap(tags: Array<TagConfigObject>) {
16
16
  // fetch the members of the concept sets and process the data
17
17
  // to return conceptToTagColorMap (wrapped in a promise).
18
18
  // Let swr cache the result of this function.
19
19
  const fetchAndMap = (url: string) => {
20
20
  const conceptSetToTagColorMap = new Map<string, string>();
21
- for (const tag of codedObsTagsConfig.tags) {
21
+ for (const tag of tags) {
22
22
  const { color, appliedToConceptSets } = tag;
23
23
  for (const answer of appliedToConceptSets ?? []) {
24
24
  if (!conceptSetToTagColorMap.has(answer)) {
@@ -44,7 +44,7 @@ export function useConceptToTagColorMap(codedObsTagsConfig: PatientCodedObsTagsE
44
44
  });
45
45
  };
46
46
 
47
- const conceptSetUuids = codedObsTagsConfig.tags.flatMap((tag) => tag.appliedToConceptSets);
47
+ const conceptSetUuids = tags.flatMap((tag) => tag.appliedToConceptSets);
48
48
  const apiUrl = `${restBaseUrl}/concept?references=${conceptSetUuids.join()}&v=${conceptSetCustomRepresentation}`;
49
49
  const conceptToTagColorMap = useSWRImmutable(apiUrl, fetchAndMap);
50
50