@kenyaemr/esm-ward-app 7.0.3-pre.89 → 8.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.
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 +3 -4
  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
@@ -1,124 +1,120 @@
1
- import { InlineNotification } from '@carbon/react';
2
- import { WorkspaceContainer, useFeatureFlag, useLocations, useSession, type Location } from '@openmrs/esm-framework';
3
1
  import React, { useMemo } from 'react';
2
+ import { InlineNotification } from '@carbon/react';
4
3
  import { useTranslation } from 'react-i18next';
5
- import { useParams } from 'react-router-dom';
4
+ import { WorkspaceContainer, useFeatureFlag } from '@openmrs/esm-framework';
6
5
  import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
7
6
  import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
8
7
  import WardBed from './ward-bed.component';
9
8
  import { bedLayoutToBed, filterBeds } from './ward-view.resource';
10
9
  import styles from './ward-view.scss';
11
10
  import WardViewHeader from '../ward-view-header/ward-view-header.component';
12
- import { type AdmittedPatient, type WardPatient } from '../types';
13
- import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
11
+ import { type InpatientAdmission, type WardPatient } from '../types';
12
+ import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
13
+ import useWardLocation from '../hooks/useWardLocation';
14
+ import UnassignedPatient from '../beds/unassigned-patient.component';
14
15
 
15
16
  const WardView = () => {
16
- const { locationUuid: locationUuidFromUrl } = useParams();
17
- const { sessionLocation } = useSession();
18
- const allLocations = useLocations();
17
+ const response = useWardLocation();
18
+ const { isLoadingLocation, invalidLocation } = response;
19
19
  const { t } = useTranslation();
20
20
  const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
21
- const locationFromUrl = allLocations.find((l) => l.uuid === locationUuidFromUrl);
22
- const invalidLocation = Boolean(locationUuidFromUrl && !locationFromUrl);
23
- const location = (locationFromUrl ?? sessionLocation) as any as Location;
24
- //TODO:Display patients with admitted status (based on their observations) that have no beds assigned
25
- if (!isBedManagementModuleInstalled) {
21
+
22
+ if (isLoadingLocation) {
26
23
  return <></>;
27
24
  }
28
25
 
29
- return invalidLocation ? (
30
- <InlineNotification
31
- kind="error"
32
- lowContrast={true}
33
- title={t('invalidLocationSpecified', 'Invalid location specified')}
34
- subtitle={t('unknownLocationUuid', 'Unknown location uuid: {{locationUuidFromUrl}}', {
35
- locationUuidFromUrl,
36
- })}
37
- />
38
- ) : (
26
+ if (invalidLocation) {
27
+ return <InlineNotification kind="error" title={t('invalidLocationSpecified', 'Invalid location specified')} />;
28
+ }
29
+
30
+ return (
39
31
  <div className={styles.wardView}>
40
- <WardViewHeader location={location} />
32
+ <WardViewHeader />
41
33
  <div className={styles.wardViewMain}>
42
- <WardViewByLocation location={location} />
34
+ {isBedManagementModuleInstalled ? <WardViewWithBedManagement /> : <WardViewWithoutBedManagement />}
43
35
  </div>
44
- <WorkspaceContainer contextKey="ward" />
36
+ <WorkspaceContainer overlay contextKey="ward" />
45
37
  </div>
46
38
  );
47
39
  };
48
40
 
49
- const WardViewByLocation = ({ location }: { location: Location }) => {
50
- const {
51
- admissionLocation,
52
- isLoading: isLoadingLocation,
53
- error: errorLoadingLocation,
54
- } = useAdmissionLocation(location.uuid);
55
- const {
56
- admittedPatients,
57
- isLoading: isLoadingPatients,
58
- error: errorLoadingPatients,
59
- } = useAdmittedPatients(location.uuid);
41
+ // display to use if bed management is installed
42
+ const WardViewWithBedManagement = () => {
43
+ const { location } = useWardLocation();
44
+ const { admissionLocation, isLoading: isLoadingLocation, error: errorLoadingLocation } = useAdmissionLocation();
45
+ const { inpatientAdmissions, isLoading: isLoadingPatients, error: errorLoadingPatients } = useInpatientAdmission();
60
46
  const { t } = useTranslation();
61
- const admittedPatientsByUuid = useMemo(() => {
62
- const map = new Map<string, AdmittedPatient>();
63
- for (const admittedPatient of admittedPatients ?? []) {
64
- map.set(admittedPatient.patient.uuid, admittedPatient);
47
+ const inpatientAdmissionsByPatientUuid = useMemo(() => {
48
+ const map = new Map<string, InpatientAdmission>();
49
+ for (const inpatientAdmission of inpatientAdmissions ?? []) {
50
+ map.set(inpatientAdmission.patient.uuid, inpatientAdmission);
65
51
  }
66
52
  return map;
67
- }, [admittedPatients]);
68
-
69
- if (admissionLocation != null && admittedPatients != null) {
70
- const bedLayouts = filterBeds(admissionLocation);
53
+ }, [inpatientAdmissions]);
71
54
 
72
- const wardBeds = bedLayouts.map((bedLayout, i) => {
55
+ if (admissionLocation != null || inpatientAdmissions != null) {
56
+ const bedLayouts = admissionLocation && filterBeds(admissionLocation);
57
+ // iterate over all beds
58
+ const wardBeds = bedLayouts?.map((bedLayout) => {
73
59
  const { patients } = bedLayout;
74
60
  const bed = bedLayoutToBed(bedLayout);
75
- const wardPatients: WardPatient[] = patients.map((patient) => {
76
- const admittedPatient = admittedPatientsByUuid.get(patient.uuid);
77
-
78
- if (admittedPatient) {
79
- // ideally, we can just use the patient object within admittedPatient
80
- // and not need the one from bedLayouts, however, the emr api
81
- // does not respect custom representation right now and does not return
82
- // all required fields for the patient object
83
- return { ...admittedPatient, admitted: true };
61
+ const wardPatients: WardPatient[] = patients.map((patient): WardPatient => {
62
+ const inpatientAdmission = inpatientAdmissionsByPatientUuid.get(patient.uuid);
63
+ if (inpatientAdmission) {
64
+ const { patient, visit } = inpatientAdmission;
65
+ return { patient, visit, bed, inpatientAdmission, inpatientRequest: null };
66
+ } else {
67
+ // for some reason this patient is in a bed but not in the list of admitted patients, so we need to use the patient data from the bed endpoint
68
+ return {
69
+ patient: patient,
70
+ visit: null,
71
+ bed,
72
+ inpatientAdmission: null, // populate after BED-13
73
+ inpatientRequest: null,
74
+ };
84
75
  }
85
-
86
- // patient assigned a bed but *not* admitted
87
- // TODO: get the patient's visit and current location
88
- return {
89
- patient,
90
- visit: null,
91
- admitted: true,
92
- currentLocation: null,
93
- timeSinceAdmissionInMinutes: null,
94
- timeAtInpatientLocationInMinutes: null,
95
- };
96
76
  });
97
77
  return <WardBed key={bed.uuid} bed={bed} wardPatients={wardPatients} />;
98
78
  });
99
79
 
80
+ const patientsInBedsUuids = bedLayouts?.flatMap((bedLayout) => bedLayout.patients.map((patient) => patient.uuid));
81
+ const wardUnassignedPatients =
82
+ inpatientAdmissions &&
83
+ inpatientAdmissions
84
+ .filter(
85
+ (inpatientAdmission) =>
86
+ !patientsInBedsUuids || !patientsInBedsUuids.includes(inpatientAdmission.patient.uuid),
87
+ )
88
+ .map((inpatientAdmission) => {
89
+ return (
90
+ <UnassignedPatient
91
+ wardPatient={{
92
+ patient: inpatientAdmission.patient,
93
+ visit: inpatientAdmission.visit,
94
+ bed: null,
95
+ inpatientAdmission,
96
+ inpatientRequest: null,
97
+ }}
98
+ key={inpatientAdmission.patient.uuid}
99
+ />
100
+ );
101
+ });
102
+
100
103
  return (
101
104
  <>
102
105
  {wardBeds}
103
- {bedLayouts.length == 0 && (
106
+ {bedLayouts?.length == 0 && (
104
107
  <InlineNotification
105
108
  kind="warning"
106
109
  lowContrast={true}
107
110
  title={t('noBedsConfigured', 'No beds configured for this location')}
108
111
  />
109
112
  )}
113
+ {wardUnassignedPatients}
110
114
  </>
111
115
  );
112
116
  } else if (isLoadingLocation || isLoadingPatients) {
113
- return (
114
- <>
115
- {Array(20)
116
- .fill(0)
117
- .map((_, i) => (
118
- <EmptyBedSkeleton key={i} />
119
- ))}
120
- </>
121
- );
117
+ return <EmptyBeds />;
122
118
  } else if (errorLoadingLocation) {
123
119
  return (
124
120
  <InlineNotification
@@ -143,4 +139,46 @@ const WardViewByLocation = ({ location }: { location: Location }) => {
143
139
  }
144
140
  };
145
141
 
142
+ // display to use if not using bed management
143
+ const WardViewWithoutBedManagement = () => {
144
+ const { inpatientAdmissions, isLoading: isLoadingPatients, error: errorLoadingPatients } = useInpatientAdmission();
145
+ const { t } = useTranslation();
146
+
147
+ if (inpatientAdmissions) {
148
+ const wardPatients = inpatientAdmissions?.map((inpatientAdmission) => {
149
+ const { patient, visit } = inpatientAdmission;
150
+ return (
151
+ <UnassignedPatient
152
+ wardPatient={{ patient, visit, bed: null, inpatientAdmission, inpatientRequest: null }}
153
+ key={inpatientAdmission.patient.uuid}
154
+ />
155
+ );
156
+ });
157
+ return <>{wardPatients}</>;
158
+ } else if (isLoadingPatients) {
159
+ return <EmptyBeds />;
160
+ } else {
161
+ return (
162
+ <InlineNotification
163
+ kind="error"
164
+ lowContrast={true}
165
+ title={t('errorLoadingPatients', 'Error loading admitted patients')}
166
+ subtitle={errorLoadingPatients?.message}
167
+ />
168
+ );
169
+ }
170
+ };
171
+
172
+ const EmptyBeds = () => {
173
+ return (
174
+ <>
175
+ {Array(20)
176
+ .fill(0)
177
+ .map((_, i) => (
178
+ <EmptyBedSkeleton key={i} />
179
+ ))}
180
+ </>
181
+ );
182
+ };
183
+
146
184
  export default WardView;
@@ -1,4 +1,4 @@
1
- import type { AdmissionLocation, Bed, BedLayout } from '../types';
1
+ import type { AdmissionLocationFetchResponse, Bed, BedLayout } from '../types';
2
2
 
3
3
  // the server side has 2 slightly incompatible types for Bed
4
4
  export function bedLayoutToBed(bedLayout: BedLayout): Bed {
@@ -13,7 +13,7 @@ export function bedLayoutToBed(bedLayout: BedLayout): Bed {
13
13
  };
14
14
  }
15
15
 
16
- export function filterBeds(admissionLocation: AdmissionLocation): BedLayout[] {
16
+ export function filterBeds(admissionLocation: AdmissionLocationFetchResponse): BedLayout[] {
17
17
  // admissionLocation.bedLayouts can contain row+column positions with no bed,
18
18
  // filter out layout positions with no real bed
19
19
  let collator = new Intl.Collator([], { numeric: true });
@@ -1,4 +1,4 @@
1
- @use '@carbon/styles/scss/spacing';
1
+ @use '@carbon/layout';
2
2
 
3
3
  .wardView {
4
4
  background-color: #e4e4e4;
@@ -9,13 +9,13 @@
9
9
  right: 0;
10
10
  display: flex;
11
11
  flex-direction: column;
12
- padding: 0 spacing.$spacing-05;
12
+ padding: 0 layout.$spacing-05;
13
13
  }
14
14
 
15
15
  .wardViewMain {
16
16
  background-color: #e4e4e4;
17
17
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
18
18
  display: grid;
19
- gap: spacing.$spacing-05;
20
- padding: spacing.$spacing-05;
19
+ gap: layout.$spacing-05;
20
+ padding: layout.$spacing-05;
21
21
  }
@@ -1,60 +1,43 @@
1
- import {
2
- type Person,
3
- type ConfigSchema,
4
- getDefaultsFromConfigSchema,
5
- useConfig,
6
- useSession,
7
- useFeatureFlag,
8
- } from '@openmrs/esm-framework';
9
- import { screen } from '@testing-library/react';
10
1
  import React from 'react';
2
+ import { screen } from '@testing-library/react';
3
+ import { type ConfigSchema, getDefaultsFromConfigSchema, useConfig, useFeatureFlag } from '@openmrs/esm-framework';
11
4
  import { useParams } from 'react-router-dom';
12
- import { mockLocations } from '../../../../__mocks__/locations.mock';
13
- import { mockAdmissionLocation } from '../../../../__mocks__/wards.mock';
14
- import { renderWithSwr } from '../../../../tools/test-utils';
5
+ import { mockAdmissionLocation, mockInpatientAdmissions } from '__mocks__';
6
+ import { renderWithSwr } from 'tools';
15
7
  import { configSchema } from '../config-schema';
16
8
  import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
9
+ import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
10
+ import useWardLocation from '../hooks/useWardLocation';
17
11
  import WardView from './ward-view.component';
18
- import { mockPatientAlice } from '../../../../__mocks__/patient.mock';
19
- import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
20
-
21
- jest.replaceProperty(mockPatientAlice.person as Person, 'preferredName', {
22
- uuid: '',
23
- givenName: 'Alice',
24
- familyName: 'Johnson',
25
- });
26
12
 
27
13
  jest.mocked(useConfig).mockReturnValue({
28
14
  ...getDefaultsFromConfigSchema<ConfigSchema>(configSchema),
29
15
  });
30
16
 
31
- const mockedSessionLocation = { uuid: 'abcd', display: 'mock location', links: [] };
32
- jest.mocked(useSession).mockReturnValue({
33
- sessionLocation: mockedSessionLocation,
34
- authenticated: true,
35
- sessionId: 'sessionId',
36
- });
17
+ const mockUseFeatureFlag = jest.mocked(useFeatureFlag);
37
18
 
38
- const mockedUseFeatureFlag = useFeatureFlag as jest.Mock;
19
+ jest.mock('../hooks/useWardLocation', () =>
20
+ jest.fn().mockReturnValue({
21
+ location: { uuid: 'abcd', display: 'mock location' },
22
+ isLoadingLocation: false,
23
+ errorFetchingLocation: null,
24
+ invalidLocation: false,
25
+ }),
26
+ );
39
27
 
40
- jest.mock('@openmrs/esm-framework', () => {
41
- return {
42
- ...jest.requireActual('@openmrs/esm-framework'),
43
- useLocations: jest.fn().mockImplementation(() => mockLocations.data.results),
44
- };
45
- });
28
+ const mockUseWardLocation = jest.mocked(useWardLocation);
46
29
 
47
30
  jest.mock('react-router-dom', () => ({
48
31
  ...jest.requireActual('react-router-dom'),
49
32
  useParams: jest.fn().mockReturnValue({}),
50
33
  }));
51
- const mockedUseParams = useParams as jest.Mock;
34
+ const mockUseParams = useParams as jest.Mock;
52
35
 
53
36
  jest.mock('../hooks/useAdmissionLocation', () => ({
54
37
  useAdmissionLocation: jest.fn(),
55
38
  }));
56
- jest.mock('../hooks/useAdmittedPatients', () => ({
57
- useAdmittedPatients: jest.fn(),
39
+ jest.mock('../hooks/useInpatientAdmission', () => ({
40
+ useInpatientAdmission: jest.fn(),
58
41
  }));
59
42
 
60
43
  jest.mocked(useAdmissionLocation).mockReturnValue({
@@ -64,26 +47,25 @@ jest.mocked(useAdmissionLocation).mockReturnValue({
64
47
  isLoading: false,
65
48
  admissionLocation: mockAdmissionLocation,
66
49
  });
67
- jest.mocked(useAdmittedPatients).mockReturnValue({
50
+ jest.mocked(useInpatientAdmission).mockReturnValue({
68
51
  error: undefined,
69
52
  mutate: jest.fn(),
70
53
  isValidating: false,
71
54
  isLoading: false,
72
- admittedPatients: [],
55
+ inpatientAdmissions: mockInpatientAdmissions,
73
56
  });
74
57
 
75
- describe('WardView:', () => {
58
+ describe('WardView', () => {
76
59
  it('renders the session location when no location provided in URL', () => {
77
60
  renderWithSwr(<WardView />);
78
- const header = screen.getByRole('heading', { name: mockedSessionLocation.display });
61
+ const header = screen.getByRole('heading', { name: 'mock location' });
79
62
  expect(header).toBeInTheDocument();
80
63
  });
81
64
 
82
65
  it('renders the location provided in URL', () => {
83
- const locationToUse = mockLocations.data.results[0];
84
- mockedUseParams.mockReturnValueOnce({ locationUuid: locationToUse.uuid });
66
+ mockUseParams.mockReturnValueOnce({ locationUuid: 'abcd' });
85
67
  renderWithSwr(<WardView />);
86
- const header = screen.getByRole('heading', { name: locationToUse.display });
68
+ const header = screen.getByRole('heading', { name: 'mock location' });
87
69
  expect(header).toBeInTheDocument();
88
70
  });
89
71
 
@@ -93,18 +75,63 @@ describe('WardView:', () => {
93
75
  expect(emptyBedCards).toHaveLength(3);
94
76
  });
95
77
 
78
+ it('renders admitted patient without bed', async () => {
79
+ renderWithSwr(<WardView />);
80
+ const admittedPatientWithoutBed = screen.queryByText('Brian Johnson');
81
+ expect(admittedPatientWithoutBed).toBeInTheDocument();
82
+ });
83
+
84
+ it('renders all admitted patients even if bed management module not installed', async () => {
85
+ mockUseFeatureFlag.mockReturnValueOnce(false);
86
+ renderWithSwr(<WardView />);
87
+ const admittedPatientWithoutBed = screen.queryByText('Brian Johnson');
88
+ expect(admittedPatientWithoutBed).toBeInTheDocument();
89
+ });
90
+
96
91
  it('renders notification for invalid location uuid', () => {
97
- mockedUseParams.mockReturnValueOnce({ locationUuid: 'invalid-uuid' });
92
+ mockUseWardLocation.mockReturnValueOnce({
93
+ location: null,
94
+ isLoadingLocation: false,
95
+ errorFetchingLocation: null,
96
+ invalidLocation: true,
97
+ });
98
+
98
99
  renderWithSwr(<WardView />);
99
100
  const notification = screen.getByRole('status');
100
101
  expect(notification).toBeInTheDocument();
101
- const invalidText = screen.getByText('Unknown location uuid: invalid-uuid');
102
+ const invalidText = screen.queryByText('Invalid location specified');
102
103
  expect(invalidText).toBeInTheDocument();
103
104
  });
104
105
 
105
- it('screen should be empty if backend module is not installed', () => {
106
- mockedUseFeatureFlag.mockReturnValueOnce(false);
107
- const { container } = renderWithSwr(<WardView />);
108
- expect(container.firstChild).not.toBeInTheDocument();
106
+ it('screen should render warning if backend module installed and no beds configured', () => {
107
+ // override the default response so that no beds are returned
108
+ jest.mocked(useAdmissionLocation).mockReturnValue({
109
+ error: undefined,
110
+ mutate: jest.fn(),
111
+ isValidating: false,
112
+ isLoading: false,
113
+ admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] },
114
+ });
115
+ mockUseFeatureFlag.mockReturnValueOnce(true);
116
+
117
+ renderWithSwr(<WardView />);
118
+ const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
119
+ expect(noBedsConfiguredForThisLocation).toBeInTheDocument();
120
+ });
121
+
122
+ it('screen not should render warning if backend module installed and no beds configured', () => {
123
+ // override the default response so that no beds are returned
124
+ jest.mocked(useAdmissionLocation).mockReturnValue({
125
+ error: undefined,
126
+ mutate: jest.fn(),
127
+ isValidating: false,
128
+ isLoading: false,
129
+ admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] },
130
+ });
131
+ mockUseFeatureFlag.mockReturnValueOnce(false);
132
+
133
+ renderWithSwr(<WardView />);
134
+ const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
135
+ expect(noBedsConfiguredForThisLocation).not.toBeInTheDocument();
109
136
  });
110
137
  });
@@ -1,45 +1,46 @@
1
- import { SkeletonIcon } from '@carbon/react';
2
- import { Movement } from '@carbon/react/icons';
3
- import { launchWorkspace, showNotification, type Location } from '@openmrs/esm-framework';
4
1
  import React from 'react';
5
- import { useTranslation } from 'react-i18next';
2
+ import { Movement } from '@carbon/react/icons';
3
+ import { Button, InlineNotification } from '@carbon/react';
4
+ import { ArrowRightIcon, isDesktop, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
6
5
  import { useInpatientRequest } from '../hooks/useInpatientRequest';
6
+ import { useTranslation } from 'react-i18next';
7
7
  import styles from './admission-requests.scss';
8
8
 
9
- interface AdmissionRequestsBarProps {
10
- location: Location;
11
- }
12
-
13
- const AdmissionRequestsBar: React.FC<AdmissionRequestsBarProps> = ({ location }) => {
14
- const { inpatientRequests, isLoading, error } = useInpatientRequest(location.uuid);
15
- const admissionRequests = inpatientRequests?.filter((request) => request.type == 'ADMISSION');
9
+ const AdmissionRequestsBar = () => {
10
+ const { inpatientRequests, isLoading, error } = useInpatientRequest(['ADMIT', 'TRANSFER']);
16
11
  const { t } = useTranslation();
12
+ const layout = useLayoutType();
17
13
 
18
- if (isLoading) {
19
- return <SkeletonIcon className={styles.skeleton} />;
14
+ if (isLoading || !inpatientRequests?.length) {
15
+ return null;
20
16
  }
21
17
 
22
18
  if (error) {
23
- showNotification({
24
- kind: 'error',
25
- title: t('errorLoadingPatientAdmissionRequests', 'Error Loading Patient Admission Requests'),
26
- description: error.message,
27
- });
28
- return <></>;
19
+ console.error(error);
20
+ return (
21
+ <InlineNotification
22
+ kind="error"
23
+ title={t('errorLoadingPatientAdmissionRequests', 'Error Loading patient admission requests')}
24
+ />
25
+ );
29
26
  }
30
27
 
31
- return admissionRequests.length > 0 ? (
28
+ return (
32
29
  <div className={styles.admissionRequestsContainer}>
33
30
  <Movement className={styles.movementIcon} size="24" />
34
- <span className={styles.content}>{admissionRequests.length} admission requests</span>
35
- <button
36
- className={styles.manageButton}
37
- onClick={() => launchWorkspace('admission-requests-cards', { admissionRequests })}>
38
- Manage
39
- </button>
31
+ <span className={styles.content}>
32
+ {t('admissionRequestsCount', '{{count}} admission request(s)', {
33
+ count: inpatientRequests.length,
34
+ })}
35
+ </span>
36
+ <Button
37
+ onClick={() => launchWorkspace('admission-requests-workspace')}
38
+ renderIcon={ArrowRightIcon}
39
+ kind="ghost"
40
+ size={isDesktop(layout) ? 'sm' : 'lg'}>
41
+ {t('manage', 'Manage')}
42
+ </Button>
40
43
  </div>
41
- ) : (
42
- <></>
43
44
  );
44
45
  };
45
46
 
@@ -1,23 +1,16 @@
1
+ import React from 'react';
1
2
  import userEvent from '@testing-library/user-event';
2
- import { renderWithSwr } from '../../../../tools/test-utils';
3
3
  import { screen } from '@testing-library/react';
4
4
  import { launchWorkspace } from '@openmrs/esm-framework';
5
- import React from 'react';
6
- import AdmissionRequestsBar from './admission-requests-bar.component';
7
- import { mockInpatientRequest } from '../../../../__mocks__/ward-patient';
5
+ import { renderWithSwr } from 'tools';
6
+ import { mockInpatientRequest } from '__mocks__';
8
7
  import { useInpatientRequest } from '../hooks/useInpatientRequest';
9
- import { mockLocationInpatientWard } from '../../../../__mocks__/locations.mock';
10
-
11
- jest.mock('@openmrs/esm-framework', () => {
12
- return {
13
- ...jest.requireActual('@openmrs/esm-framework'),
14
- launchWorkspace: jest.fn(),
15
- };
16
- });
8
+ import AdmissionRequestsBar from './admission-requests-bar.component';
17
9
 
18
10
  jest.mock('../hooks/useInpatientRequest', () => ({
19
11
  useInpatientRequest: jest.fn(),
20
12
  }));
13
+
21
14
  const mockInpatientRequestResponse = {
22
15
  error: undefined,
23
16
  mutate: jest.fn(),
@@ -25,18 +18,21 @@ const mockInpatientRequestResponse = {
25
18
  isLoading: false,
26
19
  inpatientRequests: [mockInpatientRequest],
27
20
  };
21
+
28
22
  jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
29
23
 
30
24
  describe('Admission Requests Button', () => {
31
25
  it('call launch workspace when clicked on manage button', async () => {
32
26
  const user = userEvent.setup();
33
- renderWithSwr(<AdmissionRequestsBar location={mockLocationInpatientWard} />);
27
+ renderWithSwr(<AdmissionRequestsBar />);
28
+
34
29
  await user.click(screen.getByRole('button', { name: /manage/i }));
35
30
  expect(launchWorkspace).toHaveBeenCalled();
36
31
  });
37
32
 
38
33
  it('there should be one admission request', () => {
39
- const { getByText } = renderWithSwr(<AdmissionRequestsBar location={mockLocationInpatientWard} />);
40
- expect(getByText('1 admission requests')).toBeInTheDocument();
34
+ renderWithSwr(<AdmissionRequestsBar />);
35
+
36
+ expect(screen.getByText('1 admission request(s)')).toBeInTheDocument();
41
37
  });
42
38
  });
@@ -1,42 +1,37 @@
1
- @use '@carbon/styles/scss/type';
2
- @use '@carbon/styles/scss/spacing';
3
- @import '~@openmrs/esm-styleguide/src/vars';
1
+ @use '@carbon/type';
2
+ @use '@carbon/layout';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
4
 
5
5
  .admissionRequestsContainer {
6
- width: fit-content;
7
- border-left: 2px solid $color-blue-60-2;
8
6
  background-color: $color-gray-70;
9
7
  display: flex;
10
8
  align-items: center;
11
- padding: spacing.$spacing-02;
9
+ padding: layout.$spacing-02 0 layout.$spacing-02 layout.$spacing-04;
10
+ background-color: #393939;
11
+
12
+ & > button {
13
+ color: #78a9ff;
14
+
15
+ svg {
16
+ fill: #78a9ff !important;
17
+ }
18
+ }
19
+
20
+ & > button:hover {
21
+ color: #78a9ff;
22
+ }
12
23
  }
13
24
 
14
25
  .movementIcon {
15
- padding: spacing.$spacing-02;
26
+ padding: layout.$spacing-02;
16
27
  border-radius: 50%;
17
28
  fill: $ui-03;
18
29
  background-color: $color-blue-60-2;
19
- }
20
-
21
- .manageButton {
22
- background-color: transparent;
23
- border: none;
24
- color: $inverse-link;
25
- cursor: pointer;
26
- margin-left: spacing.$spacing-04;
27
- &::after {
28
- content: '→';
29
- padding: spacing.$spacing-02;
30
- }
30
+ margin-right: layout.$spacing-03;
31
31
  }
32
32
 
33
33
  .content {
34
34
  @include type.type-style('heading-compact-01');
35
35
  color: $ui-02;
36
- margin-left: spacing.$spacing-02;
37
- }
38
-
39
- .skeleton {
40
- height: 20px;
41
- width: 120px;
36
+ margin-right: layout.$spacing-03;
42
37
  }