@kenyaemr/esm-ward-app 8.1.1-pre.114 → 8.1.1-pre.116

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 (143) hide show
  1. package/.turbo/turbo-build.log +18 -17
  2. package/dist/109.js +1 -0
  3. package/dist/109.js.map +1 -0
  4. package/dist/125.js +1 -0
  5. package/dist/125.js.map +1 -0
  6. package/dist/126.js +1 -0
  7. package/dist/126.js.map +1 -0
  8. package/dist/130.js +1 -1
  9. package/dist/130.js.map +1 -1
  10. package/dist/146.js +1 -0
  11. package/dist/146.js.map +1 -0
  12. package/dist/15.js +1 -0
  13. package/dist/15.js.map +1 -0
  14. package/dist/161.js +2 -0
  15. package/dist/161.js.map +1 -0
  16. package/dist/269.js +1 -1
  17. package/dist/269.js.map +1 -1
  18. package/dist/466.js +1 -1
  19. package/dist/466.js.map +1 -1
  20. package/dist/500.js +1 -0
  21. package/dist/500.js.map +1 -0
  22. package/dist/53.js +1 -0
  23. package/dist/53.js.map +1 -0
  24. package/dist/557.js +1 -0
  25. package/dist/557.js.map +1 -0
  26. package/dist/559.js +1 -0
  27. package/dist/559.js.map +1 -0
  28. package/dist/574.js +1 -1
  29. package/dist/577.js +1 -1
  30. package/dist/577.js.map +1 -1
  31. package/dist/659.js +1 -1
  32. package/dist/659.js.map +1 -1
  33. package/dist/701.js +1 -0
  34. package/dist/701.js.map +1 -0
  35. package/dist/749.js +1 -1
  36. package/dist/749.js.map +1 -1
  37. package/dist/908.js +1 -0
  38. package/dist/908.js.map +1 -0
  39. package/dist/922.js +1 -0
  40. package/dist/922.js.map +1 -0
  41. package/dist/969.js +1 -0
  42. package/dist/969.js.map +1 -0
  43. package/dist/kenyaemr-esm-ward-app.js +1 -1
  44. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +294 -74
  45. package/dist/kenyaemr-esm-ward-app.js.map +1 -1
  46. package/dist/main.js +1 -1
  47. package/dist/main.js.map +1 -1
  48. package/dist/routes.json +1 -1
  49. package/mock.tsx +54 -0
  50. package/package.json +1 -1
  51. package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +37 -0
  52. package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +20 -0
  53. package/src/beds/empty-bed-skeleton.tsx +2 -1
  54. package/src/beds/empty-bed.scss +0 -4
  55. package/src/beds/occupied-bed.scss +1 -0
  56. package/src/config-schema-mother-child-row.ts +26 -0
  57. package/src/config-schema-pending-items-extension.ts +29 -0
  58. package/src/config-schema.ts +12 -14
  59. package/src/hooks/useAdmissionLocation.ts +22 -4
  60. package/src/hooks/useBeds.ts +3 -4
  61. package/src/hooks/useConcept.ts +3 -4
  62. package/src/hooks/useEmrConfiguration.ts +5 -0
  63. package/src/hooks/useInpatientAdmission.ts +9 -14
  64. package/src/hooks/useInpatientRequest.ts +4 -15
  65. package/src/hooks/useLocations.ts +8 -51
  66. package/src/hooks/useMotherAndChildren.ts +46 -0
  67. package/src/hooks/useObs.ts +2 -6
  68. package/src/hooks/usePatientPendingOrders.ts +16 -0
  69. package/src/hooks/useWardPatientGrouping.ts +25 -0
  70. package/src/index.ts +50 -3
  71. package/src/location-selector/location-selector.component.tsx +18 -21
  72. package/src/routes.json +43 -0
  73. package/src/types/index.ts +34 -0
  74. package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +7 -2
  75. package/src/ward-patient-card/card-rows/mother-child-row.extension.tsx +110 -0
  76. package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
  77. package/src/ward-patient-card/card-rows/pending-items-car-row.extension.tsx +50 -0
  78. package/src/ward-patient-card/row-elements/ward-pateint-skeleton-text.tsx +9 -0
  79. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +1 -1
  80. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +54 -36
  81. package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +2 -3
  82. package/src/ward-patient-card/row-elements/ward-patient-location.tsx +19 -0
  83. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +36 -32
  84. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +15 -9
  85. package/src/ward-patient-card/row-elements/ward-patient-pending-order.component.tsx +45 -0
  86. package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.tsx +38 -0
  87. package/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx +32 -0
  88. package/src/ward-patient-card/ward-patient-card-element.component.tsx +4 -0
  89. package/src/ward-patient-card/ward-patient-card.component.tsx +21 -14
  90. package/src/ward-patient-card/ward-patient-card.scss +61 -8
  91. package/src/ward-patient-card/ward-patient-resource.ts +15 -0
  92. package/src/ward-view/ward-view.component.tsx +124 -132
  93. package/src/ward-view/ward-view.resource.ts +121 -1
  94. package/src/ward-view/ward-view.scss +16 -6
  95. package/src/ward-view/ward-view.test.tsx +27 -42
  96. package/src/ward-view-header/admission-requests-bar.component.tsx +8 -7
  97. package/src/ward-view-header/admission-requests-bar.test.tsx +8 -21
  98. package/src/ward-view-header/admission-requests.scss +1 -1
  99. package/src/ward-view-header/ward-metric.component.tsx +24 -0
  100. package/src/ward-view-header/ward-metric.scss +25 -0
  101. package/src/ward-view-header/ward-metrics.component.tsx +77 -0
  102. package/src/ward-view-header/ward-metrics.scss +8 -0
  103. package/src/ward-view-header/ward-metrics.test.tsx +91 -0
  104. package/src/ward-view-header/ward-view-header.component.tsx +3 -0
  105. package/src/ward-view-header/ward-view-header.scss +0 -1
  106. package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +11 -3
  107. package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +4 -5
  108. package/src/ward-workspace/admission-request-card/admission-request-card.scss +8 -4
  109. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +8 -3
  110. package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +2 -0
  111. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +29 -61
  112. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +37 -21
  113. package/src/ward-workspace/patient-banner/patient-banner.component.tsx +3 -3
  114. package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +23 -0
  115. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient-action-button.extension.tsx +2 -1
  116. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.workspace.tsx +7 -5
  117. package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
  118. package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +120 -0
  119. package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +40 -30
  120. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +29 -22
  121. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +12 -2
  122. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +2 -2
  123. package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +11 -0
  124. package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +1 -1
  125. package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +1 -1
  126. package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +2 -2
  127. package/src/ward-workspace/ward-patient-notes/notes.resource.ts +5 -7
  128. package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +1 -1
  129. package/src/ward-workspace/ward-patient-notes/types.ts +0 -4
  130. package/src/ward.resource.ts +6 -0
  131. package/translations/en.json +18 -1
  132. package/dist/346.js +0 -1
  133. package/dist/346.js.map +0 -1
  134. package/dist/76.js +0 -1
  135. package/dist/76.js.map +0 -1
  136. package/dist/803.js +0 -1
  137. package/dist/803.js.map +0 -1
  138. package/dist/958.js +0 -2
  139. package/dist/958.js.map +0 -1
  140. package/dist/960.js +0 -1
  141. package/dist/960.js.map +0 -1
  142. /package/dist/{958.js.LICENSE.txt → 161.js.LICENSE.txt} +0 -0
  143. /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
@@ -1,23 +1,26 @@
1
- import React, { useMemo } from 'react';
2
1
  import { InlineNotification } from '@carbon/react';
2
+ import { useAppContext, useDefineAppContext, useFeatureFlag, WorkspaceContainer } from '@openmrs/esm-framework';
3
+ import React, { useEffect, useRef } from 'react';
3
4
  import { useTranslation } from 'react-i18next';
4
- import { WorkspaceContainer, useFeatureFlag } from '@openmrs/esm-framework';
5
5
  import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
6
- import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
6
+ import UnassignedPatient from '../beds/unassigned-patient.component';
7
+ import useWardLocation from '../hooks/useWardLocation';
8
+ import { useWardPatientGrouping } from '../hooks/useWardPatientGrouping';
9
+ import { type WardPatient, type WardPatientGroupDetails } from '../types';
10
+ import WardViewHeader from '../ward-view-header/ward-view-header.component';
7
11
  import WardBed from './ward-bed.component';
8
- import { bedLayoutToBed, filterBeds } from './ward-view.resource';
12
+ import { bedLayoutToBed } from './ward-view.resource';
9
13
  import styles from './ward-view.scss';
10
- import WardViewHeader from '../ward-view-header/ward-view-header.component';
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
+ import classNames from 'classnames';
15
15
 
16
16
  const WardView = () => {
17
17
  const response = useWardLocation();
18
18
  const { isLoadingLocation, invalidLocation } = response;
19
19
  const { t } = useTranslation();
20
- const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
20
+
21
+ const wardPatientsGroupDetails = useWardPatientGrouping();
22
+ useDefineAppContext<WardPatientGroupDetails>('ward-patients-group', wardPatientsGroupDetails);
23
+ const isVertical = useFeatureFlag('ward-view-vertical-tiling');
21
24
 
22
25
  if (isLoadingLocation) {
23
26
  return <></>;
@@ -28,145 +31,134 @@ const WardView = () => {
28
31
  }
29
32
 
30
33
  return (
31
- <div className={styles.wardView}>
32
- <WardViewHeader />
33
- <div className={styles.wardViewMain}>
34
- {isBedManagementModuleInstalled ? <WardViewWithBedManagement /> : <WardViewWithoutBedManagement />}
34
+ <>
35
+ <div className={classNames(styles.wardView, { [styles.verticalTiling]: isVertical })}>
36
+ <WardViewHeader />
37
+ <WardViewMain />
35
38
  </div>
36
39
  <WorkspaceContainer overlay contextKey="ward" />
37
- </div>
40
+ </>
38
41
  );
39
42
  };
40
43
 
41
- // display to use if bed management is installed
42
- const WardViewWithBedManagement = () => {
44
+ const WardViewMain = () => {
43
45
  const { location } = useWardLocation();
44
- const { admissionLocation, isLoading: isLoadingLocation, error: errorLoadingLocation } = useAdmissionLocation();
45
- const { inpatientAdmissions, isLoading: isLoadingPatients, error: errorLoadingPatients } = useInpatientAdmission();
46
46
  const { t } = useTranslation();
47
- const inpatientAdmissionsByPatientUuid = useMemo(() => {
48
- const map = new Map<string, InpatientAdmission>();
49
- for (const inpatientAdmission of inpatientAdmissions ?? []) {
50
- map.set(inpatientAdmission.patient.uuid, inpatientAdmission);
51
- }
52
- return map;
53
- }, [inpatientAdmissions]);
47
+ const isVertical = useFeatureFlag('ward-view-vertical-tiling');
54
48
 
55
- if (admissionLocation != null || inpatientAdmissions != null) {
56
- const bedLayouts = admissionLocation && filterBeds(admissionLocation);
57
- // iterate over all beds
58
- const wardBeds = bedLayouts?.map((bedLayout) => {
59
- const { patients } = bedLayout;
60
- const bed = bedLayoutToBed(bedLayout);
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
- };
75
- }
76
- });
77
- return <WardBed key={bed.uuid} bed={bed} wardPatients={wardPatients} />;
78
- });
49
+ const wardPatientsGrouping = useAppContext<WardPatientGroupDetails>('ward-patients-group');
50
+ const { bedLayouts, wardAdmittedPatientsWithBed, wardUnassignedPatientsList } = wardPatientsGrouping ?? {};
51
+ const { isLoading: isLoadingAdmissionLocation, error: errorLoadingAdmissionLocation } =
52
+ wardPatientsGrouping?.admissionLocationResponse ?? {};
53
+ const {
54
+ isLoading: isLoadingInpatientAdmissions,
55
+ error: errorLoadingInpatientAdmissions,
56
+ hasMore: hasMoreInpatientAdmissions,
57
+ loadMore: loadMoreInpatientAdmissions,
58
+ } = wardPatientsGrouping?.inpatientAdmissionResponse ?? {};
59
+ const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
79
60
 
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
- });
61
+ const scrollToLoadMoreTrigger = useRef<HTMLDivElement>(null);
62
+ useEffect(
63
+ function scrollToLoadMore() {
64
+ const observer = new IntersectionObserver(
65
+ (entries) => {
66
+ entries.forEach((entry) => {
67
+ if (entry.isIntersecting) {
68
+ if (hasMoreInpatientAdmissions && !errorLoadingInpatientAdmissions && !isLoadingInpatientAdmissions) {
69
+ loadMoreInpatientAdmissions();
70
+ }
71
+ }
72
+ });
73
+ },
74
+ { threshold: 1 },
75
+ );
102
76
 
103
- return (
104
- <>
105
- {wardBeds}
106
- {bedLayouts?.length == 0 && (
107
- <InlineNotification
108
- kind="warning"
109
- lowContrast={true}
110
- title={t('noBedsConfigured', 'No beds configured for this location')}
111
- />
112
- )}
113
- {wardUnassignedPatients}
114
- </>
115
- );
116
- } else if (isLoadingLocation || isLoadingPatients) {
117
- return <EmptyBeds />;
118
- } else if (errorLoadingLocation) {
119
- return (
120
- <InlineNotification
121
- kind="error"
122
- lowContrast={true}
123
- title={t('errorLoadingWardLocation', 'Error loading ward location')}
124
- subtitle={
125
- errorLoadingLocation?.message ??
126
- t('invalidWardLocation', 'Invalid ward location: {{location}}', { location: location.display })
77
+ if (scrollToLoadMoreTrigger.current) {
78
+ observer.observe(scrollToLoadMoreTrigger.current);
79
+ }
80
+ return () => {
81
+ if (scrollToLoadMoreTrigger.current) {
82
+ observer.unobserve(scrollToLoadMoreTrigger.current);
127
83
  }
128
- />
129
- );
130
- } else {
131
- return (
132
- <InlineNotification
133
- kind="error"
134
- lowContrast={true}
135
- title={t('errorLoadingPatients', 'Error loading admitted patients')}
136
- subtitle={errorLoadingPatients?.message}
137
- />
138
- );
139
- }
140
- };
84
+ };
85
+ },
86
+ [scrollToLoadMoreTrigger, hasMoreInpatientAdmissions, errorLoadingInpatientAdmissions, loadMoreInpatientAdmissions],
87
+ );
141
88
 
142
- // display to use if not using bed management
143
- const WardViewWithoutBedManagement = () => {
144
- const { inpatientAdmissions, isLoading: isLoadingPatients, error: errorLoadingPatients } = useInpatientAdmission();
145
- const { t } = useTranslation();
89
+ if (!wardPatientsGrouping) return <></>;
146
90
 
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
- );
91
+ const wardBeds = bedLayouts?.map((bedLayout) => {
92
+ const { patients } = bedLayout;
93
+ const bed = bedLayoutToBed(bedLayout);
94
+ const wardPatients: WardPatient[] = patients.map((patient): WardPatient => {
95
+ const inpatientAdmission = wardAdmittedPatientsWithBed.get(patient.uuid);
96
+ if (inpatientAdmission) {
97
+ const { patient, visit, currentInpatientRequest } = inpatientAdmission;
98
+ return { patient, visit, bed, inpatientAdmission, inpatientRequest: currentInpatientRequest || null };
99
+ } else {
100
+ // 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
101
+ return {
102
+ patient: patient,
103
+ visit: null,
104
+ bed,
105
+ inpatientAdmission: null, // populate after BED-13
106
+ inpatientRequest: null,
107
+ };
108
+ }
156
109
  });
157
- return <>{wardPatients}</>;
158
- } else if (isLoadingPatients) {
159
- return <EmptyBeds />;
160
- } else {
110
+ return <WardBed key={bed.uuid} bed={bed} wardPatients={wardPatients} />;
111
+ });
112
+
113
+ const wardUnassignedPatients = wardUnassignedPatientsList?.map((inpatientAdmission) => {
161
114
  return (
162
- <InlineNotification
163
- kind="error"
164
- lowContrast={true}
165
- title={t('errorLoadingPatients', 'Error loading admitted patients')}
166
- subtitle={errorLoadingPatients?.message}
115
+ <UnassignedPatient
116
+ wardPatient={{
117
+ patient: inpatientAdmission.patient,
118
+ visit: inpatientAdmission.visit,
119
+ bed: null,
120
+ inpatientAdmission,
121
+ inpatientRequest: null,
122
+ }}
123
+ key={inpatientAdmission.patient.uuid}
167
124
  />
168
125
  );
169
- }
126
+ });
127
+
128
+ return (
129
+ <div className={classNames(styles.wardViewMain, { [styles.verticalTiling]: isVertical })}>
130
+ {wardBeds}
131
+ {bedLayouts?.length == 0 && isBedManagementModuleInstalled && (
132
+ <InlineNotification
133
+ kind="warning"
134
+ lowContrast={true}
135
+ title={t('noBedsConfigured', 'No beds configured for this location')}
136
+ />
137
+ )}
138
+ {wardUnassignedPatients}
139
+ {(isLoadingAdmissionLocation || isLoadingInpatientAdmissions) && <EmptyBeds />}
140
+ {errorLoadingAdmissionLocation && (
141
+ <InlineNotification
142
+ kind="error"
143
+ lowContrast={true}
144
+ title={t('errorLoadingWardLocation', 'Error loading ward location')}
145
+ subtitle={
146
+ errorLoadingAdmissionLocation?.message ??
147
+ t('invalidWardLocation', 'Invalid ward location: {{location}}', { location: location.display })
148
+ }
149
+ />
150
+ )}
151
+ {errorLoadingInpatientAdmissions && (
152
+ <InlineNotification
153
+ kind="error"
154
+ lowContrast={true}
155
+ title={t('errorLoadingPatients', 'Error loading admitted patients')}
156
+ subtitle={errorLoadingInpatientAdmissions?.message}
157
+ />
158
+ )}
159
+ <div ref={scrollToLoadMoreTrigger}></div>
160
+ </div>
161
+ );
170
162
  };
171
163
 
172
164
  const EmptyBeds = () => {
@@ -1,4 +1,14 @@
1
- import type { AdmissionLocationFetchResponse, Bed, BedLayout } from '../types';
1
+ import { type Patient } from '@openmrs/esm-framework';
2
+ import type {
3
+ AdmissionLocationFetchResponse,
4
+ Bed,
5
+ BedLayout,
6
+ InpatientAdmission,
7
+ InpatientRequest,
8
+ WardMetrics,
9
+ WardPatientGroupDetails,
10
+ } from '../types';
11
+ import type { TFunction } from 'i18next';
2
12
 
3
13
  // the server side has 2 slightly incompatible types for Bed
4
14
  export function bedLayoutToBed(bedLayout: BedLayout): Bed {
@@ -20,5 +30,115 @@ export function filterBeds(admissionLocation: AdmissionLocationFetchResponse): B
20
30
  const bedLayouts = admissionLocation.bedLayouts
21
31
  .filter((bl) => bl.bedId)
22
32
  .sort((bedA, bedB) => collator.compare(bedA.bedNumber, bedB.bedNumber));
33
+
23
34
  return bedLayouts;
24
35
  }
36
+
37
+ //TODO: This implementation will change when the api is ready
38
+ export function getWardMetrics(bedLayouts: BedLayout[], wardPatientGroup: WardPatientGroupDetails): WardMetrics {
39
+ const bedMetrics = {
40
+ patients: '--',
41
+ freeBeds: '--',
42
+ capacity: '--',
43
+ };
44
+ if (bedLayouts == null || bedLayouts.length == 0) return bedMetrics;
45
+ const total = bedLayouts.length;
46
+ const occupiedBeds = bedLayouts.filter((bed) => bed.patients.length>0);
47
+ const patients = occupiedBeds.length;
48
+ const freeBeds = total - patients;
49
+ const capacity = total != 0 ? Math.trunc((wardPatientGroup.totalPatientsCount / total) * 100) : 0;
50
+ return {
51
+ patients: wardPatientGroup?.totalPatientsCount.toString() ?? '--',
52
+ freeBeds: freeBeds.toString(),
53
+ capacity: capacity.toString(),
54
+ };
55
+ }
56
+
57
+ export function getInpatientAdmissionsUuidMap(inpatientAdmissions: InpatientAdmission[]) {
58
+ const map = new Map<string, InpatientAdmission>();
59
+ for (const inpatientAdmission of inpatientAdmissions ?? []) {
60
+ map.set(inpatientAdmission.patient.uuid, inpatientAdmission);
61
+ }
62
+ return map;
63
+ }
64
+
65
+ export function createAndGetWardPatientGrouping(
66
+ inpatientAdmissions: InpatientAdmission[],
67
+ admissionLocation: AdmissionLocationFetchResponse,
68
+ inpatientRequests: InpatientRequest[],
69
+ ) {
70
+ const inpatientAdmissionsByPatientUuid = getInpatientAdmissionsUuidMap(inpatientAdmissions);
71
+
72
+ const wardAdmittedPatientsWithBed = new Map<string, InpatientAdmission>();
73
+ const wardUnadmittedPatientsWithBed = new Map<string, Patient>();
74
+ const bedLayouts = admissionLocation && filterBeds(admissionLocation);
75
+ const allWardPatientUuids = new Set<string>();
76
+ let wardPatientPendingCount = 0;
77
+ bedLayouts?.map((bedLayout) => {
78
+ const { patients } = bedLayout;
79
+ patients.map((patient) => {
80
+ const patientAdmittedWithBed = inpatientAdmissionsByPatientUuid.get(patient.uuid);
81
+ allWardPatientUuids.add(patient.uuid);
82
+ if (patientAdmittedWithBed) {
83
+ wardAdmittedPatientsWithBed.set(patient.uuid, patientAdmittedWithBed);
84
+ //count the pending metric
85
+ const dispositionType = patientAdmittedWithBed.currentInpatientRequest?.dispositionType;
86
+ if (dispositionType == 'TRANSFER' || dispositionType == 'DISCHARGE') wardPatientPendingCount++;
87
+ } else {
88
+ wardUnadmittedPatientsWithBed.set(patient.uuid, patient);
89
+ }
90
+ });
91
+ });
92
+
93
+ const wardUnassignedPatientsList =
94
+ inpatientAdmissions?.filter((inpatientAdmission) => {
95
+ allWardPatientUuids.add(inpatientAdmission.patient.uuid);
96
+ return (
97
+ !wardAdmittedPatientsWithBed.has(inpatientAdmission.patient.uuid) &&
98
+ !wardUnadmittedPatientsWithBed.has(inpatientAdmission.patient.uuid)
99
+ );
100
+ }) ?? [];
101
+
102
+ //excluding inpatientRequests
103
+ const totalPatientsCount = allWardPatientUuids.size;
104
+
105
+ for (const inpatientRequest of inpatientRequests ?? []) {
106
+ allWardPatientUuids.add(inpatientRequest.patient.uuid);
107
+ }
108
+
109
+ return {
110
+ wardAdmittedPatientsWithBed,
111
+ wardUnadmittedPatientsWithBed,
112
+ wardPatientPendingCount,
113
+ bedLayouts,
114
+ wardUnassignedPatientsList,
115
+ allWardPatientUuids,
116
+ totalPatientsCount,
117
+ };
118
+ }
119
+
120
+ export function getWardMetricNameTranslation(name: string, t: TFunction) {
121
+ switch (name) {
122
+ case 'patients':
123
+ return t('patients', 'Patients');
124
+ case 'freeBeds':
125
+ return t('freeBeds', 'Free beds');
126
+ case 'capacity':
127
+ return t('capacity', 'Capacity');
128
+ case 'pendingOut':
129
+ return t('pendingOut', 'Pending out');
130
+ }
131
+ }
132
+
133
+ export function getWardMetricValueTranslation(name: string, t: TFunction, value: string) {
134
+ switch (name) {
135
+ case 'patients':
136
+ return t('patientsMetricValue', '{{ metricValue }}', { metricValue: value });
137
+ case 'freeBeds':
138
+ return t('freeBedsMetricValue', '{{ metricValue }}', { metricValue: value });
139
+ case 'capacity':
140
+ return t('capacityMetricValue', '{{ metricValue }} %', { metricValue: value });
141
+ case 'pendingOut':
142
+ return t('pendingOutMetricValue', '{{ metricValue }}', { metricValue: value });
143
+ }
144
+ }
@@ -1,15 +1,15 @@
1
1
  @use '@carbon/layout';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
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,15 @@
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
+
26
+ > div {
27
+ break-inside: avoid;
28
+ margin-bottom: 10px;
29
+ }
30
+ }
21
31
  }
@@ -1,12 +1,16 @@
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 } from '../../mock';
7
13
  import { configSchema } from '../config-schema';
8
- import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
9
- import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
10
14
  import useWardLocation from '../hooks/useWardLocation';
11
15
  import WardView from './ward-view.component';
12
16
 
@@ -33,29 +37,16 @@ jest.mock('react-router-dom', () => ({
33
37
  }));
34
38
  const mockUseParams = useParams as jest.Mock;
35
39
 
36
- jest.mock('../hooks/useAdmissionLocation', () => ({
37
- useAdmissionLocation: jest.fn(),
38
- }));
39
- jest.mock('../hooks/useInpatientAdmission', () => ({
40
- useInpatientAdmission: jest.fn(),
41
- }));
40
+ jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails());
42
41
 
43
- jest.mocked(useAdmissionLocation).mockReturnValue({
44
- error: undefined,
45
- mutate: jest.fn(),
46
- isValidating: false,
47
- isLoading: false,
48
- admissionLocation: mockAdmissionLocation,
49
- });
50
- jest.mocked(useInpatientAdmission).mockReturnValue({
51
- error: undefined,
52
- mutate: jest.fn(),
53
- isValidating: false,
54
- isLoading: false,
55
- inpatientAdmissions: mockInpatientAdmissions,
42
+ const intersectionObserverMock = () => ({
43
+ observe: () => null,
56
44
  });
45
+ window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
57
46
 
58
47
  describe('WardView', () => {
48
+ let replacedProperty: jest.ReplaceProperty<any> | null = null;
49
+
59
50
  it('renders the session location when no location provided in URL', () => {
60
51
  renderWithSwr(<WardView />);
61
52
  const header = screen.getByRole('heading', { name: 'mock location' });
@@ -103,35 +94,29 @@ describe('WardView', () => {
103
94
  expect(invalidText).toBeInTheDocument();
104
95
  });
105
96
 
106
- it('screen should render warning if backend module installed and no beds configured', () => {
97
+ it('should render warning if backend module installed and no beds configured', () => {
107
98
  // 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);
99
+ replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
100
+
101
+ mockUseFeatureFlag.mockReturnValue(true);
116
102
 
117
103
  renderWithSwr(<WardView />);
118
104
  const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
119
105
  expect(noBedsConfiguredForThisLocation).toBeInTheDocument();
120
106
  });
121
107
 
122
- it('screen not should render warning if backend module installed and no beds configured', () => {
108
+ it('should not render warning if backend module installed and no beds configured', () => {
123
109
  // 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);
110
+ replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
111
+ mockUseFeatureFlag.mockReturnValue(false);
132
112
 
133
113
  renderWithSwr(<WardView />);
134
114
  const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
135
115
  expect(noBedsConfiguredForThisLocation).not.toBeInTheDocument();
136
116
  });
117
+
118
+ afterEach(() => {
119
+ replacedProperty?.restore();
120
+ replacedProperty = null;
121
+ });
137
122
  });
@@ -1,13 +1,14 @@
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 from 'react';
6
5
  import { useTranslation } from 'react-i18next';
6
+ import { type WardPatientGroupDetails } from '../types';
7
7
  import styles from './admission-requests.scss';
8
8
 
9
9
  const AdmissionRequestsBar = () => {
10
- const { inpatientRequests, isLoading, error } = useInpatientRequest(['ADMIT', 'TRANSFER']);
10
+ const wardPatientGrouping = useAppContext<WardPatientGroupDetails>('ward-patients-group');
11
+ const { inpatientRequests, isLoading, error } = wardPatientGrouping?.inpatientRequestResponse ?? {};
11
12
  const { t } = useTranslation();
12
13
  const layout = useLayoutType();
13
14
 
@@ -20,7 +21,7 @@ const AdmissionRequestsBar = () => {
20
21
  return (
21
22
  <InlineNotification
22
23
  kind="error"
23
- title={t('errorLoadingPatientAdmissionRequests', 'Error Loading patient admission requests')}
24
+ title={t('errorLoadingPatientAdmissionRequests', 'Error loading patient admission requests')}
24
25
  />
25
26
  );
26
27
  }
@@ -29,7 +30,7 @@ const AdmissionRequestsBar = () => {
29
30
  <div className={styles.admissionRequestsContainer}>
30
31
  <Movement className={styles.movementIcon} size="24" />
31
32
  <span className={styles.content}>
32
- {t('admissionRequestsCount', '{{count}} admission request(s)', {
33
+ {t('admissionRequestsCount', '{{count}} admission request', {
33
34
  count: inpatientRequests.length,
34
35
  })}
35
36
  </span>
@@ -1,28 +1,15 @@
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 { mockWardPatientGroupDetails } from '../../mock';
8
7
  import AdmissionRequestsBar from './admission-requests-bar.component';
9
8
 
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);
9
+ jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails());
23
10
 
24
11
  describe('Admission Requests Button', () => {
25
- it('call launch workspace when clicked on manage button', async () => {
12
+ it('should launch workspace when clicked on manage button', async () => {
26
13
  const user = userEvent.setup();
27
14
  renderWithSwr(<AdmissionRequestsBar />);
28
15
 
@@ -30,9 +17,9 @@ describe('Admission Requests Button', () => {
30
17
  expect(launchWorkspace).toHaveBeenCalled();
31
18
  });
32
19
 
33
- it('there should be one admission request', () => {
20
+ it('should have one admission request', () => {
34
21
  renderWithSwr(<AdmissionRequestsBar />);
35
22
 
36
- expect(screen.getByText('1 admission request(s)')).toBeInTheDocument();
23
+ expect(screen.getByText('1 admission request')).toBeInTheDocument();
37
24
  });
38
25
  });
@@ -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