@kenyaemr/esm-ward-app 8.0.1-pre.95 → 8.0.2

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 (196) hide show
  1. package/.turbo/turbo-build.log +20 -24
  2. package/dist/109.js +1 -0
  3. package/dist/109.js.map +1 -0
  4. package/dist/124.js +1 -0
  5. package/dist/124.js.map +1 -0
  6. package/dist/125.js +1 -0
  7. package/dist/125.js.map +1 -0
  8. package/dist/130.js +1 -1
  9. package/dist/130.js.LICENSE.txt +2 -0
  10. package/dist/130.js.map +1 -1
  11. package/dist/146.js +1 -0
  12. package/dist/146.js.map +1 -0
  13. package/dist/15.js +1 -0
  14. package/dist/15.js.map +1 -0
  15. package/dist/153.js +1 -0
  16. package/dist/153.js.map +1 -0
  17. package/dist/303.js +2 -1
  18. package/dist/303.js.map +1 -1
  19. package/dist/325.js +1 -0
  20. package/dist/325.js.map +1 -0
  21. package/dist/372.js +2 -0
  22. package/dist/372.js.map +1 -0
  23. package/dist/471.js +1 -0
  24. package/dist/471.js.map +1 -0
  25. package/dist/481.js +1 -0
  26. package/dist/481.js.map +1 -0
  27. package/dist/53.js +1 -0
  28. package/dist/53.js.map +1 -0
  29. package/dist/{960.js → 559.js} +1 -1
  30. package/dist/559.js.map +1 -0
  31. package/dist/574.js +1 -1
  32. package/dist/576.js +1 -0
  33. package/dist/576.js.map +1 -0
  34. package/dist/577.js +1 -1
  35. package/dist/577.js.map +1 -1
  36. package/dist/{255.js → 649.js} +2 -2
  37. package/dist/649.js.LICENSE.txt +9 -0
  38. package/dist/649.js.map +1 -0
  39. package/dist/{659.js → 662.js} +1 -1
  40. package/dist/662.js.map +1 -0
  41. package/dist/920.js +1 -0
  42. package/dist/920.js.map +1 -0
  43. package/dist/921.js +1 -0
  44. package/dist/921.js.map +1 -0
  45. package/dist/922.js +1 -0
  46. package/dist/922.js.map +1 -0
  47. package/dist/969.js +1 -0
  48. package/dist/969.js.map +1 -0
  49. package/dist/kenyaemr-esm-ward-app.js +1 -1
  50. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +304 -128
  51. package/dist/kenyaemr-esm-ward-app.js.map +1 -1
  52. package/dist/main.js +1 -1
  53. package/dist/main.js.LICENSE.txt +0 -10
  54. package/dist/main.js.map +1 -1
  55. package/dist/routes.json +1 -1
  56. package/mock.tsx +62 -0
  57. package/package-lock.json +5001 -0
  58. package/package.json +2 -3
  59. package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +37 -0
  60. package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +20 -0
  61. package/src/beds/empty-bed-skeleton.tsx +4 -3
  62. package/src/beds/empty-bed.component.tsx +3 -3
  63. package/src/beds/ward-bed.component.tsx +41 -0
  64. package/src/beds/ward-bed.scss +45 -0
  65. package/src/beds/{occupied-bed.test.tsx → ward-bed.test.tsx} +42 -20
  66. package/src/config-schema.ts +203 -84
  67. package/src/constant.ts +1 -1
  68. package/src/hooks/useAdmissionLocation.ts +22 -4
  69. package/src/hooks/useAssignedBedByPatient.ts +9 -0
  70. package/src/hooks/useBeds.ts +3 -4
  71. package/src/hooks/useConcept.ts +3 -4
  72. package/src/hooks/useEmrConfiguration.ts +5 -0
  73. package/src/hooks/useInpatientAdmission.ts +9 -14
  74. package/src/hooks/useInpatientRequest.ts +4 -15
  75. package/src/hooks/useLocations.ts +8 -51
  76. package/src/hooks/useMotherAndChildren.ts +46 -0
  77. package/src/hooks/useObs.ts +3 -7
  78. package/src/hooks/usePatientPendingOrders.ts +16 -0
  79. package/src/hooks/useWardPatientGrouping.ts +32 -0
  80. package/src/index.ts +45 -17
  81. package/src/location-selector/location-selector.component.tsx +18 -21
  82. package/src/root.component.tsx +3 -0
  83. package/src/routes.json +41 -3
  84. package/src/types/index.ts +50 -1
  85. package/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx +38 -0
  86. package/src/ward-patient-card/card-rows/coded-obs-tags-row.component.tsx +108 -0
  87. package/src/ward-patient-card/card-rows/mother-child-row.component.tsx +84 -0
  88. package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
  89. package/src/ward-patient-card/card-rows/pending-items-row.component.tsx +54 -0
  90. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +1 -1
  91. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +62 -39
  92. package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +5 -5
  93. package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +18 -41
  94. package/src/ward-patient-card/row-elements/ward-patient-location.tsx +19 -0
  95. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +38 -34
  96. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +26 -13
  97. package/src/ward-patient-card/row-elements/ward-patient-pending-order.component.tsx +45 -0
  98. package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.tsx +38 -0
  99. package/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx +32 -0
  100. package/src/ward-patient-card/row-elements/ward-patient-skeleton-text.tsx +9 -0
  101. package/src/ward-patient-card/ward-patient-card.component.tsx +14 -45
  102. package/src/ward-patient-card/ward-patient-card.scss +68 -9
  103. package/src/ward-view/default-ward/default-ward-beds.component.tsx +42 -0
  104. package/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx +32 -0
  105. package/src/ward-view/default-ward/default-ward-patient-card.component.tsx +31 -0
  106. package/src/ward-view/default-ward/default-ward-pending-patients.component.tsx +52 -0
  107. package/src/ward-view/default-ward/default-ward-unassigned-patients.component.tsx +32 -0
  108. package/src/ward-view/default-ward/default-ward-view.component.tsx +31 -0
  109. package/src/ward-view/materal-ward/maternal-ward-beds.component.tsx +65 -0
  110. package/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx +30 -0
  111. package/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx +93 -0
  112. package/src/{beds/occupied-bed.scss → ward-view/materal-ward/maternal-ward-patient-card.scss} +4 -9
  113. package/src/ward-view/materal-ward/maternal-ward-patient-card.test.tsx +58 -0
  114. package/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx +48 -0
  115. package/src/ward-view/materal-ward/maternal-ward-unassigned-patients.component.tsx +33 -0
  116. package/src/ward-view/materal-ward/maternal-ward-view.component.tsx +38 -0
  117. package/src/ward-view/materal-ward/maternal-ward-view.resource.ts +89 -0
  118. package/src/ward-view/ward-view.component.tsx +15 -163
  119. package/src/ward-view/ward-view.resource.ts +193 -1
  120. package/src/ward-view/ward-view.scss +17 -6
  121. package/src/ward-view/ward-view.test.tsx +43 -48
  122. package/src/ward-view/ward.component.tsx +106 -0
  123. package/src/ward-view-header/admission-requests-bar.component.tsx +14 -9
  124. package/src/ward-view-header/admission-requests-bar.test.tsx +11 -23
  125. package/src/ward-view-header/admission-requests.scss +1 -1
  126. package/src/ward-view-header/ward-metric.component.tsx +24 -0
  127. package/src/ward-view-header/ward-metric.scss +25 -0
  128. package/src/ward-view-header/ward-metrics.component.tsx +78 -0
  129. package/src/ward-view-header/ward-metrics.scss +7 -0
  130. package/src/ward-view-header/ward-metrics.test.tsx +37 -0
  131. package/src/ward-view-header/ward-view-header.component.tsx +9 -4
  132. package/src/ward-view-header/ward-view-header.scss +0 -1
  133. package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +70 -6
  134. package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +10 -23
  135. package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +9 -3
  136. package/src/ward-workspace/admission-request-card/admission-request-card.scss +13 -4
  137. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +55 -33
  138. package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +30 -37
  139. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +98 -203
  140. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +116 -180
  141. package/src/ward-workspace/bed-selector.component.tsx +119 -0
  142. package/src/ward-workspace/bed-selector.scss +15 -0
  143. package/src/ward-workspace/patient-banner/patient-banner.component.tsx +7 -14
  144. package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +23 -0
  145. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient-action-button.extension.tsx +2 -1
  146. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.workspace.tsx +18 -9
  147. package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
  148. package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +113 -0
  149. package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +68 -79
  150. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +24 -24
  151. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +12 -2
  152. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +12 -8
  153. package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +11 -0
  154. package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +1 -1
  155. package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +1 -1
  156. package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +2 -2
  157. package/src/ward-workspace/ward-patient-notes/notes.resource.ts +5 -7
  158. package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +1 -1
  159. package/src/ward-workspace/ward-patient-notes/types.ts +0 -4
  160. package/src/ward.resource.ts +38 -2
  161. package/translations/en.json +31 -7
  162. package/dist/152.js +0 -1
  163. package/dist/152.js.map +0 -1
  164. package/dist/255.js.map +0 -1
  165. package/dist/269.js +0 -1
  166. package/dist/269.js.map +0 -1
  167. package/dist/346.js +0 -1
  168. package/dist/346.js.map +0 -1
  169. package/dist/466.js +0 -1
  170. package/dist/466.js.map +0 -1
  171. package/dist/659.js.map +0 -1
  172. package/dist/729.js +0 -1
  173. package/dist/729.js.map +0 -1
  174. package/dist/749.js +0 -1
  175. package/dist/749.js.map +0 -1
  176. package/dist/76.js +0 -1
  177. package/dist/76.js.map +0 -1
  178. package/dist/793.js +0 -2
  179. package/dist/793.js.map +0 -1
  180. package/dist/803.js +0 -1
  181. package/dist/803.js.map +0 -1
  182. package/dist/960.js.map +0 -1
  183. package/src/beds/empty-bed.scss +0 -28
  184. package/src/beds/occupied-bed.component.tsx +0 -35
  185. package/src/beds/unassigned-patient.component.tsx +0 -20
  186. package/src/beds/unassigned-patient.scss +0 -6
  187. package/src/config-schema-admission-request-note.ts +0 -9
  188. package/src/config-schema-extension-colored-obs-tags.ts +0 -91
  189. package/src/hooks/useCurrentWardCardConfig.ts +0 -32
  190. package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +0 -27
  191. package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +0 -13
  192. package/src/ward-patient-card/ward-patient-card-element.component.tsx +0 -65
  193. package/src/ward-view/ward-bed.component.tsx +0 -14
  194. /package/dist/{793.js.LICENSE.txt → 303.js.LICENSE.txt} +0 -0
  195. /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
  196. /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
@@ -1,15 +1,15 @@
1
1
  @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
2
3
 
3
4
  .wardView {
4
5
  background-color: #e4e4e4;
5
- position: absolute;
6
- top: var(--omrs-topnav-height);
7
- bottom: 0;
8
- left: 0;
9
- right: 0;
10
6
  display: flex;
11
7
  flex-direction: column;
12
8
  padding: 0 layout.$spacing-05;
9
+
10
+ &.verticalTiling {
11
+ height: calc(100vh - var(--omrs-topnav-height));
12
+ }
13
13
  }
14
14
 
15
15
  .wardViewMain {
@@ -17,5 +17,16 @@
17
17
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
18
18
  display: grid;
19
19
  gap: layout.$spacing-05;
20
- padding: layout.$spacing-05;
20
+
21
+ &.verticalTiling {
22
+ display: block;
23
+ column-width: 280px;
24
+ overflow-x: auto;
25
+ height: calc(100% - 4rem);
26
+
27
+ > div {
28
+ break-inside: avoid;
29
+ margin-bottom: 10px;
30
+ }
31
+ }
21
32
  }
@@ -1,13 +1,20 @@
1
- import React from 'react';
1
+ import {
2
+ type ConfigSchema,
3
+ getDefaultsFromConfigSchema,
4
+ useAppContext,
5
+ useConfig,
6
+ useFeatureFlag,
7
+ } from '@openmrs/esm-framework';
2
8
  import { screen } from '@testing-library/react';
3
- import { type ConfigSchema, getDefaultsFromConfigSchema, useConfig, useFeatureFlag } from '@openmrs/esm-framework';
9
+ import React from 'react';
4
10
  import { useParams } from 'react-router-dom';
5
- import { mockAdmissionLocation, mockInpatientAdmissions } from '__mocks__';
6
11
  import { renderWithSwr } from 'tools';
12
+ import { mockWardPatientGroupDetails, mockWardViewContext } from '../../mock';
7
13
  import { configSchema } from '../config-schema';
8
- import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
9
- import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
14
+ import { useObs } from '../hooks/useObs';
10
15
  import useWardLocation from '../hooks/useWardLocation';
16
+ import { type WardViewContext } from '../types';
17
+ import DefaultWardView from './default-ward/default-ward-view.component';
11
18
  import WardView from './ward-view.component';
12
19
 
13
20
  jest.mocked(useConfig).mockReturnValue({
@@ -24,6 +31,9 @@ jest.mock('../hooks/useWardLocation', () =>
24
31
  invalidLocation: false,
25
32
  }),
26
33
  );
34
+ jest.mock('../hooks/useObs', () => ({
35
+ useObs: jest.fn(),
36
+ }));
27
37
 
28
38
  const mockUseWardLocation = jest.mocked(useWardLocation);
29
39
 
@@ -33,57 +43,48 @@ jest.mock('react-router-dom', () => ({
33
43
  }));
34
44
  const mockUseParams = useParams as jest.Mock;
35
45
 
36
- jest.mock('../hooks/useAdmissionLocation', () => ({
37
- useAdmissionLocation: jest.fn(),
38
- }));
39
- jest.mock('../hooks/useInpatientAdmission', () => ({
40
- useInpatientAdmission: jest.fn(),
41
- }));
42
-
43
- jest.mocked(useAdmissionLocation).mockReturnValue({
44
- error: undefined,
45
- mutate: jest.fn(),
46
- isValidating: false,
47
- isLoading: false,
48
- admissionLocation: mockAdmissionLocation,
46
+ jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
47
+ //@ts-ignore
48
+ jest.mocked(useObs).mockReturnValue({
49
+ data: [],
49
50
  });
50
- jest.mocked(useInpatientAdmission).mockReturnValue({
51
- error: undefined,
52
- mutate: jest.fn(),
53
- isValidating: false,
54
- isLoading: false,
55
- inpatientAdmissions: mockInpatientAdmissions,
51
+
52
+ const intersectionObserverMock = () => ({
53
+ observe: () => null,
56
54
  });
55
+ window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
57
56
 
58
57
  describe('WardView', () => {
58
+ let replacedProperty: jest.ReplaceProperty<any> | null = null;
59
+
59
60
  it('renders the session location when no location provided in URL', () => {
60
- renderWithSwr(<WardView />);
61
+ renderWithSwr(<DefaultWardView />);
61
62
  const header = screen.getByRole('heading', { name: 'mock location' });
62
63
  expect(header).toBeInTheDocument();
63
64
  });
64
65
 
65
66
  it('renders the location provided in URL', () => {
66
67
  mockUseParams.mockReturnValueOnce({ locationUuid: 'abcd' });
67
- renderWithSwr(<WardView />);
68
+ renderWithSwr(<DefaultWardView />);
68
69
  const header = screen.getByRole('heading', { name: 'mock location' });
69
70
  expect(header).toBeInTheDocument();
70
71
  });
71
72
 
72
73
  it('renders the correct number of occupied and empty beds', async () => {
73
- renderWithSwr(<WardView />);
74
+ renderWithSwr(<DefaultWardView />);
74
75
  const emptyBedCards = await screen.findAllByText(/empty bed/i);
75
76
  expect(emptyBedCards).toHaveLength(3);
76
77
  });
77
78
 
78
79
  it('renders admitted patient without bed', async () => {
79
- renderWithSwr(<WardView />);
80
+ renderWithSwr(<DefaultWardView />);
80
81
  const admittedPatientWithoutBed = screen.queryByText('Brian Johnson');
81
82
  expect(admittedPatientWithoutBed).toBeInTheDocument();
82
83
  });
83
84
 
84
85
  it('renders all admitted patients even if bed management module not installed', async () => {
85
86
  mockUseFeatureFlag.mockReturnValueOnce(false);
86
- renderWithSwr(<WardView />);
87
+ renderWithSwr(<DefaultWardView />);
87
88
  const admittedPatientWithoutBed = screen.queryByText('Brian Johnson');
88
89
  expect(admittedPatientWithoutBed).toBeInTheDocument();
89
90
  });
@@ -103,35 +104,29 @@ describe('WardView', () => {
103
104
  expect(invalidText).toBeInTheDocument();
104
105
  });
105
106
 
106
- it('screen should render warning if backend module installed and no beds configured', () => {
107
+ it('should render warning if backend module installed and no beds configured', () => {
107
108
  // 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);
109
+ replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
116
110
 
117
- renderWithSwr(<WardView />);
111
+ mockUseFeatureFlag.mockReturnValue(true);
112
+
113
+ renderWithSwr(<DefaultWardView />);
118
114
  const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
119
115
  expect(noBedsConfiguredForThisLocation).toBeInTheDocument();
120
116
  });
121
117
 
122
- it('screen not should render warning if backend module installed and no beds configured', () => {
118
+ it('should not render warning if backend module installed and no beds configured', () => {
123
119
  // 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);
120
+ replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
121
+ mockUseFeatureFlag.mockReturnValue(false);
132
122
 
133
123
  renderWithSwr(<WardView />);
134
124
  const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
135
125
  expect(noBedsConfiguredForThisLocation).not.toBeInTheDocument();
136
126
  });
127
+
128
+ afterEach(() => {
129
+ replacedProperty?.restore();
130
+ replacedProperty = null;
131
+ });
137
132
  });
@@ -0,0 +1,106 @@
1
+ import { InlineNotification } from '@carbon/react';
2
+ import { useAppContext, useFeatureFlag } from '@openmrs/esm-framework';
3
+ import classNames from 'classnames';
4
+ import React, { useEffect, useRef, type ReactNode } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
7
+ import useWardLocation from '../hooks/useWardLocation';
8
+ import { type WardViewContext } from '../types';
9
+ import styles from './ward-view.scss';
10
+
11
+ const Ward = ({ wardBeds, wardUnassignedPatients }: { wardBeds: ReactNode; wardUnassignedPatients: ReactNode }) => {
12
+ const { location } = useWardLocation();
13
+ const { t } = useTranslation();
14
+ const isVertical = useFeatureFlag('ward-view-vertical-tiling');
15
+
16
+ const {wardPatientGroupDetails} = useAppContext<WardViewContext>('ward-view-context') ?? {};
17
+ const { bedLayouts } = wardPatientGroupDetails ?? {};
18
+ const { isLoading: isLoadingAdmissionLocation, error: errorLoadingAdmissionLocation } =
19
+ wardPatientGroupDetails?.admissionLocationResponse ?? {};
20
+ const {
21
+ isLoading: isLoadingInpatientAdmissions,
22
+ error: errorLoadingInpatientAdmissions,
23
+ hasMore: hasMoreInpatientAdmissions,
24
+ loadMore: loadMoreInpatientAdmissions,
25
+ } = wardPatientGroupDetails?.inpatientAdmissionResponse ?? {};
26
+ const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
27
+
28
+ const scrollToLoadMoreTrigger = useRef<HTMLDivElement>(null);
29
+ useEffect(
30
+ function scrollToLoadMore() {
31
+ const observer = new IntersectionObserver(
32
+ (entries) => {
33
+ entries.forEach((entry) => {
34
+ if (entry.isIntersecting) {
35
+ if (hasMoreInpatientAdmissions && !errorLoadingInpatientAdmissions && !isLoadingInpatientAdmissions) {
36
+ loadMoreInpatientAdmissions();
37
+ }
38
+ }
39
+ });
40
+ },
41
+ { threshold: 1 },
42
+ );
43
+
44
+ if (scrollToLoadMoreTrigger.current) {
45
+ observer.observe(scrollToLoadMoreTrigger.current);
46
+ }
47
+ return () => {
48
+ if (scrollToLoadMoreTrigger.current) {
49
+ observer.unobserve(scrollToLoadMoreTrigger.current);
50
+ }
51
+ };
52
+ },
53
+ [scrollToLoadMoreTrigger, hasMoreInpatientAdmissions, errorLoadingInpatientAdmissions, loadMoreInpatientAdmissions],
54
+ );
55
+
56
+ if (!wardPatientGroupDetails) return <></>;
57
+
58
+ return (
59
+ <div className={classNames(styles.wardViewMain, { [styles.verticalTiling]: isVertical })}>
60
+ {wardBeds}
61
+ {bedLayouts?.length == 0 && isBedManagementModuleInstalled && (
62
+ <InlineNotification
63
+ kind="warning"
64
+ lowContrast={true}
65
+ title={t('noBedsConfigured', 'No beds configured for this location')}
66
+ />
67
+ )}
68
+ {wardUnassignedPatients}
69
+ {(isLoadingAdmissionLocation || isLoadingInpatientAdmissions) && <EmptyBeds />}
70
+ {errorLoadingAdmissionLocation && (
71
+ <InlineNotification
72
+ kind="error"
73
+ lowContrast={true}
74
+ title={t('errorLoadingWardLocation', 'Error loading ward location')}
75
+ subtitle={
76
+ errorLoadingAdmissionLocation?.message ??
77
+ t('invalidWardLocation', 'Invalid ward location: {{location}}', { location: location.display })
78
+ }
79
+ />
80
+ )}
81
+ {errorLoadingInpatientAdmissions && (
82
+ <InlineNotification
83
+ kind="error"
84
+ lowContrast={true}
85
+ title={t('errorLoadingPatients', 'Error loading admitted patients')}
86
+ subtitle={errorLoadingInpatientAdmissions?.message}
87
+ />
88
+ )}
89
+ <div ref={scrollToLoadMoreTrigger}></div>
90
+ </div>
91
+ );
92
+ };
93
+
94
+ const EmptyBeds = () => {
95
+ return (
96
+ <>
97
+ {Array(20)
98
+ .fill(0)
99
+ .map((_, i) => (
100
+ <EmptyBedSkeleton key={i} />
101
+ ))}
102
+ </>
103
+ );
104
+ };
105
+
106
+ export default Ward;
@@ -1,13 +1,18 @@
1
- import React from 'react';
2
- import { Movement } from '@carbon/react/icons';
3
1
  import { Button, InlineNotification } from '@carbon/react';
4
- import { ArrowRightIcon, isDesktop, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
5
- import { useInpatientRequest } from '../hooks/useInpatientRequest';
2
+ import { Movement } from '@carbon/react/icons';
3
+ import { ArrowRightIcon, isDesktop, launchWorkspace, useAppContext, useLayoutType } from '@openmrs/esm-framework';
4
+ import React, { type ReactNode } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
6
+ import { type WardViewContext } from '../types';
7
7
  import styles from './admission-requests.scss';
8
8
 
9
- const AdmissionRequestsBar = () => {
10
- const { inpatientRequests, isLoading, error } = useInpatientRequest(['ADMIT', 'TRANSFER']);
9
+ interface AdmissionRequestsBarProps {
10
+ wardPendingPatients: ReactNode;
11
+ }
12
+
13
+ const AdmissionRequestsBar: React.FC<AdmissionRequestsBarProps> = ({ wardPendingPatients }) => {
14
+ const {wardPatientGroupDetails} = useAppContext<WardViewContext>('ward-view-context') ?? {};
15
+ const { inpatientRequests, isLoading, error } = wardPatientGroupDetails?.inpatientRequestResponse ?? {};
11
16
  const { t } = useTranslation();
12
17
  const layout = useLayoutType();
13
18
 
@@ -20,7 +25,7 @@ const AdmissionRequestsBar = () => {
20
25
  return (
21
26
  <InlineNotification
22
27
  kind="error"
23
- title={t('errorLoadingPatientAdmissionRequests', 'Error Loading patient admission requests')}
28
+ title={t('errorLoadingPatientAdmissionRequests', 'Error loading patient admission requests')}
24
29
  />
25
30
  );
26
31
  }
@@ -29,12 +34,12 @@ const AdmissionRequestsBar = () => {
29
34
  <div className={styles.admissionRequestsContainer}>
30
35
  <Movement className={styles.movementIcon} size="24" />
31
36
  <span className={styles.content}>
32
- {t('admissionRequestsCount', '{{count}} admission request(s)', {
37
+ {t('admissionRequestsCount', '{{count}} admission request', {
33
38
  count: inpatientRequests.length,
34
39
  })}
35
40
  </span>
36
41
  <Button
37
- onClick={() => launchWorkspace('admission-requests-workspace')}
42
+ onClick={() => launchWorkspace('admission-requests-workspace', { wardPendingPatients })}
38
43
  renderIcon={ArrowRightIcon}
39
44
  kind="ghost"
40
45
  size={isDesktop(layout) ? 'sm' : 'lg'}>
@@ -1,38 +1,26 @@
1
- import React from 'react';
2
- import userEvent from '@testing-library/user-event';
1
+ import { launchWorkspace, useAppContext } from '@openmrs/esm-framework';
3
2
  import { screen } from '@testing-library/react';
4
- import { launchWorkspace } from '@openmrs/esm-framework';
3
+ import userEvent from '@testing-library/user-event';
4
+ import React from 'react';
5
5
  import { renderWithSwr } from 'tools';
6
- import { mockInpatientRequest } from '__mocks__';
7
- import { useInpatientRequest } from '../hooks/useInpatientRequest';
6
+ import { mockWardViewContext } from '../../mock';
7
+ import { type WardViewContext } from '../types';
8
8
  import AdmissionRequestsBar from './admission-requests-bar.component';
9
9
 
10
- jest.mock('../hooks/useInpatientRequest', () => ({
11
- useInpatientRequest: jest.fn(),
12
- }));
13
-
14
- const mockInpatientRequestResponse = {
15
- error: undefined,
16
- mutate: jest.fn(),
17
- isValidating: false,
18
- isLoading: false,
19
- inpatientRequests: [mockInpatientRequest],
20
- };
21
-
22
- jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
10
+ jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
23
11
 
24
12
  describe('Admission Requests Button', () => {
25
- it('call launch workspace when clicked on manage button', async () => {
13
+ it('should launch workspace when clicked on manage button', async () => {
26
14
  const user = userEvent.setup();
27
- renderWithSwr(<AdmissionRequestsBar />);
15
+ renderWithSwr(<AdmissionRequestsBar wardPendingPatients={[]} />);
28
16
 
29
17
  await user.click(screen.getByRole('button', { name: /manage/i }));
30
18
  expect(launchWorkspace).toHaveBeenCalled();
31
19
  });
32
20
 
33
- it('there should be one admission request', () => {
34
- renderWithSwr(<AdmissionRequestsBar />);
21
+ it('should have one admission request', () => {
22
+ renderWithSwr(<AdmissionRequestsBar wardPendingPatients={[<div>Dummy Patient</div>]} />);
35
23
 
36
- expect(screen.getByText('1 admission request(s)')).toBeInTheDocument();
24
+ expect(screen.getByText('1 admission request')).toBeInTheDocument();
37
25
  });
38
26
  });
@@ -8,7 +8,7 @@
8
8
  align-items: center;
9
9
  padding: layout.$spacing-02 0 layout.$spacing-02 layout.$spacing-04;
10
10
  background-color: #393939;
11
-
11
+ margin-left: layout.$spacing-03;
12
12
  & > button {
13
13
  color: #78a9ff;
14
14
 
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import styles from './ward-metric.scss';
3
+ import { SkeletonPlaceholder } from '@carbon/react';
4
+
5
+ interface WardMetricProps {
6
+ metricName: string;
7
+ metricValue: string;
8
+ isLoading: boolean;
9
+ }
10
+ const WardMetric: React.FC<WardMetricProps> = ({ metricName, metricValue, isLoading }) => {
11
+
12
+ return (
13
+ <div className={styles.metric}>
14
+ <span className={styles.metricName}>{metricName}</span>
15
+ {isLoading ? (
16
+ <SkeletonPlaceholder className={styles.skeleton} />
17
+ ) : (
18
+ <span className={styles.metricValue}>{metricValue}</span>
19
+ )}
20
+ </div>
21
+ );
22
+ };
23
+
24
+ export default WardMetric;
@@ -0,0 +1,25 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .metric {
6
+ margin-left: layout.$spacing-05;
7
+ display: flex;
8
+ align-items: end;
9
+ gap: 5px;
10
+ }
11
+
12
+ .metricName {
13
+ @include type.type-style('helper-text-01');
14
+ color: $color-gray-70;
15
+ }
16
+
17
+ .metricValue {
18
+ @include type.type-style('heading-03');
19
+ line-height: revert;
20
+ }
21
+
22
+ .skeleton {
23
+ height: layout.$spacing-05;
24
+ width: layout.$spacing-05;
25
+ }
@@ -0,0 +1,78 @@
1
+ import { showNotification, useAppContext, useFeatureFlag } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import type { WardViewContext } from '../types';
5
+ import {
6
+ getWardMetricNameTranslation,
7
+ getWardMetrics,
8
+ getWardMetricValueTranslation,
9
+ } from '../ward-view/ward-view.resource';
10
+ import WardMetric from './ward-metric.component';
11
+ import styles from './ward-metrics.scss';
12
+
13
+ const wardMetrics = [{ name: 'patients' }, { name: 'freeBeds' }, { name: 'capacity' }];
14
+
15
+ const WardMetrics = () => {
16
+ const { t } = useTranslation();
17
+ const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
18
+ const { wardPatientGroupDetails } = useAppContext<WardViewContext>('ward-view-context') ?? {};
19
+ const { admissionLocationResponse, inpatientAdmissionResponse, inpatientRequestResponse, bedLayouts } =
20
+ wardPatientGroupDetails || {};
21
+ const { isLoading, error } = admissionLocationResponse ?? {};
22
+ const isDataLoading =
23
+ admissionLocationResponse?.isLoading ||
24
+ inpatientAdmissionResponse?.isLoading ||
25
+ inpatientRequestResponse?.isLoading;
26
+ if (!wardPatientGroupDetails) return <></>;
27
+
28
+ if (error) {
29
+ showNotification({
30
+ kind: 'error',
31
+ title: t('errorLoadingBedDetails', 'Error loading bed details'),
32
+ description: error.message,
33
+ });
34
+ }
35
+
36
+ const wardMetricValues = getWardMetrics(bedLayouts, wardPatientGroupDetails);
37
+ return (
38
+ <div className={styles.metricsContainer}>
39
+ {isBedManagementModuleInstalled ? (
40
+ wardMetrics.map((wardMetric) => {
41
+ return (
42
+ <WardMetric
43
+ metricName={getWardMetricNameTranslation(wardMetric.name, t)}
44
+ metricValue={getWardMetricValueTranslation(wardMetric.name, t, wardMetricValues[wardMetric.name])}
45
+ isLoading={!!isLoading || !!isDataLoading}
46
+ key={wardMetric.name}
47
+ />
48
+ );
49
+ })
50
+ ) : (
51
+ <WardMetric
52
+ metricName={getWardMetricNameTranslation('patients', t)}
53
+ metricValue={'--'}
54
+ isLoading={false}
55
+ key={'patients'}
56
+ />
57
+ )}
58
+ {isBedManagementModuleInstalled && (
59
+ <WardMetric
60
+ metricName={getWardMetricNameTranslation('pendingOut', t)}
61
+ metricValue={
62
+ error
63
+ ? '--'
64
+ : getWardMetricValueTranslation(
65
+ 'pendingOut',
66
+ t,
67
+ wardPatientGroupDetails?.wardPatientPendingCount?.toString(),
68
+ )
69
+ }
70
+ isLoading={!!isDataLoading}
71
+ key="pending"
72
+ />
73
+ )}
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default WardMetrics;
@@ -0,0 +1,7 @@
1
+ @use '@openmrs/esm-styleguide/src/vars' as *;
2
+
3
+ .metricsContainer {
4
+ display: flex;
5
+ align-items: end;
6
+ margin-left: auto;
7
+ }
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import WardMetrics from './ward-metrics.component';
3
+ import { renderWithSwr } from '../../../../tools/test-utils';
4
+ import {
5
+ createAndGetWardPatientGrouping,
6
+ getInpatientAdmissionsUuidMap,
7
+ getWardMetrics,
8
+ } from '../ward-view/ward-view.resource';
9
+ import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
10
+ import { mockAdmissionLocation, mockInpatientAdmissions, mockInpatientRequest } from '__mocks__';
11
+ import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
12
+ import useWardLocation from '../hooks/useWardLocation';
13
+ import { screen } from '@testing-library/react';
14
+ import { useAppContext } from '@openmrs/esm-framework';
15
+ import { type WardViewContext } from '../types';
16
+ import { mockWardViewContext } from '../../mock';
17
+
18
+ const wardMetrics = [
19
+ { name: 'patients', key: 'patients', defaultTranslation: 'Patients' },
20
+ { name: 'freeBeds', key: 'freeBeds', defaultTranslation: 'Free beds' },
21
+ { name: 'capacity', key: 'capacity', defaultTranslation: 'Capacity' },
22
+ ];
23
+
24
+ jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
25
+
26
+ describe('Ward Metrics', () => {
27
+ it('Should display metrics of in the ward ', () => {
28
+ const mockWardPatientGroupDetails = mockWardViewContext.wardPatientGroupDetails;
29
+ const { bedLayouts } = mockWardPatientGroupDetails;
30
+ const bedMetrics = getWardMetrics(bedLayouts, mockWardPatientGroupDetails);
31
+ renderWithSwr(<WardMetrics />);
32
+ for (let [key, value] of Object.entries(bedMetrics)) {
33
+ const fieldName = wardMetrics.find((metric) => metric.name == key)?.defaultTranslation;
34
+ expect(screen.getByText(fieldName!)).toBeInTheDocument();
35
+ }
36
+ });
37
+ });
@@ -1,16 +1,21 @@
1
- import React from 'react';
1
+ import React, { type ReactNode } from 'react';
2
2
  import styles from './ward-view-header.scss';
3
3
  import AdmissionRequestsBar from './admission-requests-bar.component';
4
4
  import useWardLocation from '../hooks/useWardLocation';
5
+ import WardMetrics from './ward-metrics.component';
5
6
 
6
- interface WardViewHeaderProps {}
7
+ interface WardViewHeaderProps {
8
+ wardPendingPatients: ReactNode;
9
+ }
7
10
 
8
- const WardViewHeader: React.FC<WardViewHeaderProps> = () => {
11
+ const WardViewHeader: React.FC<WardViewHeaderProps> = ({ wardPendingPatients }) => {
9
12
  const { location } = useWardLocation();
13
+
10
14
  return (
11
15
  <div className={styles.wardViewHeader}>
12
16
  <h4>{location?.display}</h4>
13
- <AdmissionRequestsBar />
17
+ <WardMetrics />
18
+ <AdmissionRequestsBar {...{ wardPendingPatients }} />
14
19
  </div>
15
20
  );
16
21
  };
@@ -4,5 +4,4 @@
4
4
  margin: layout.$spacing-05 0;
5
5
  display: flex;
6
6
  align-items: center;
7
- justify-content: space-between;
8
7
  }