@kenyaemr/esm-ward-app 8.0.1-pre.99 → 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
@@ -0,0 +1,58 @@
1
+ import { getDefaultsFromConfigSchema, useAppContext, useConfig } from '@openmrs/esm-framework';
2
+ import { screen } from '@testing-library/react';
3
+ import { mockPatientAlice, mockVisitAlice } from '__mocks__';
4
+ import React from 'react';
5
+ import { renderWithSwr } from 'tools';
6
+ import { mockInpatientAdmissionAlice } from '../../../../../__mocks__/inpatient-admission';
7
+ import { mockWardBeds } from '../../../../../__mocks__/wardBeds.mock';
8
+ import { mockWardViewContext } from '../../../mock';
9
+ import { configSchema, type WardConfigObject } from '../../config-schema';
10
+ import { useObs } from '../../hooks/useObs';
11
+ import { type WardPatient, type WardViewContext } from '../../types';
12
+ import MaternalWardPatientCard from './maternal-ward-patient-card.component';
13
+
14
+ jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
15
+ jest.mock('../../hooks/useObs', () => ({
16
+ useObs: jest.fn(),
17
+ }));
18
+ jest.mock('../../ward-patient-card/row-elements/ward-patient-obs.resource', () => ({
19
+ useConceptToTagColorMap: jest.fn(),
20
+ }));
21
+
22
+ const defaultConfig: WardConfigObject = getDefaultsFromConfigSchema(configSchema);
23
+
24
+ jest.mocked(useConfig).mockReturnValue(defaultConfig);
25
+ //@ts-ignore
26
+ jest.mocked(useObs).mockReturnValue({
27
+ data: [],
28
+ });
29
+
30
+ describe('MaternalWardPatientCard', () => {
31
+ it('renders a patient with no child', () => {
32
+ const alice: WardPatient = {
33
+ patient: mockPatientAlice,
34
+ bed: mockWardBeds[0],
35
+ inpatientAdmission: mockInpatientAdmissionAlice,
36
+ visit: mockVisitAlice,
37
+ inpatientRequest: null,
38
+ };
39
+ renderWithSwr(<MaternalWardPatientCard wardPatient={alice} childrenOfWardPatientInSameBed={[]} />);
40
+
41
+ const patientName = screen.queryByText('Alice Johnson');
42
+ expect(patientName).toBeInTheDocument();
43
+ });
44
+
45
+ it('renders a patient with another child in same bed', () => {
46
+ const alice: WardPatient = {
47
+ patient: mockPatientAlice,
48
+ bed: mockWardBeds[0],
49
+ inpatientAdmission: mockInpatientAdmissionAlice,
50
+ visit: mockVisitAlice,
51
+ inpatientRequest: null,
52
+ };
53
+ renderWithSwr(<MaternalWardPatientCard wardPatient={alice} childrenOfWardPatientInSameBed={[alice]} />);
54
+
55
+ const bedDivider = screen.queryByText('Mother / Child');
56
+ expect(bedDivider).toBeInTheDocument();
57
+ });
58
+ });
@@ -0,0 +1,48 @@
1
+ import { ErrorState, useAppContext } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { type InpatientRequest, type WardViewContext } from '../../types';
5
+ import AdmissionRequestNoteRow from '../../ward-patient-card/card-rows/admission-request-note-row.component';
6
+ import CodedObsTagsRow from '../../ward-patient-card/card-rows/coded-obs-tags-row.component';
7
+ import MotherChildRow from '../../ward-patient-card/card-rows/mother-child-row.component';
8
+ import WardPatientSkeletonText from '../../ward-patient-card/row-elements/ward-patient-skeleton-text';
9
+ import AdmissionRequestCard from '../../ward-workspace/admission-request-card/admission-request-card.component';
10
+
11
+ function MaternalWardPendingPatients() {
12
+ const { wardPatientGroupDetails } = useAppContext<WardViewContext>('ward-view-context') ?? {};
13
+ const { t } = useTranslation();
14
+ const { inpatientRequestResponse } = wardPatientGroupDetails ?? {};
15
+ const {
16
+ inpatientRequests,
17
+ isLoading: isLoadingInpatientRequests,
18
+ error: errorFetchingInpatientRequests,
19
+ } = inpatientRequestResponse ?? {};
20
+
21
+ return isLoadingInpatientRequests ? (
22
+ <WardPatientSkeletonText />
23
+ ) : errorFetchingInpatientRequests ? (
24
+ <ErrorState headerTitle={t('admissionRequests', 'Admission requests')} error={errorFetchingInpatientRequests} />
25
+ ) : (
26
+ <>
27
+ {inpatientRequests?.map((request: InpatientRequest, i) => {
28
+ const wardPatient = {
29
+ patient: request.patient,
30
+ visit: request.visit,
31
+ bed: null,
32
+ inpatientRequest: request,
33
+ inpatientAdmission: null,
34
+ };
35
+
36
+ return (
37
+ <AdmissionRequestCard key={`admission-request-card-${i}`} wardPatient={wardPatient}>
38
+ <CodedObsTagsRow id="pregnancy-complications" {...wardPatient} />
39
+ <MotherChildRow wardPatient={wardPatient} childrenOfWardPatientInSameBed={[]} />
40
+ <AdmissionRequestNoteRow id={'admission-request-note'} wardPatient={wardPatient} />
41
+ </AdmissionRequestCard>
42
+ );
43
+ })}
44
+ </>
45
+ );
46
+ }
47
+
48
+ export default MaternalWardPendingPatients;
@@ -0,0 +1,33 @@
1
+ import { useAppContext } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import { type WardViewContext } from '../../types';
4
+ import MaternalWardPatientCard from './maternal-ward-patient-card.component';
5
+
6
+ /**
7
+ * Renders a list of patients in the ward that are admitted but not assigned a bed
8
+ * @returns
9
+ */
10
+ function MaternalWardUnassignedPatients() {
11
+ const { wardPatientGroupDetails } = useAppContext<WardViewContext>('ward-view-context') ?? {};
12
+ const { wardUnassignedPatientsList } = wardPatientGroupDetails ?? {};
13
+
14
+ const wardUnassignedPatients = wardUnassignedPatientsList?.map((inpatientAdmission) => {
15
+ return (
16
+ <MaternalWardPatientCard
17
+ wardPatient={{
18
+ patient: inpatientAdmission.patient,
19
+ visit: inpatientAdmission.visit,
20
+ bed: null,
21
+ inpatientAdmission,
22
+ inpatientRequest: inpatientAdmission.currentInpatientRequest,
23
+ }}
24
+ childrenOfWardPatientInSameBed={[]}
25
+ key={inpatientAdmission.patient.uuid}
26
+ />
27
+ );
28
+ });
29
+
30
+ return <>{wardUnassignedPatients}</>;
31
+ }
32
+
33
+ export default MaternalWardUnassignedPatients;
@@ -0,0 +1,38 @@
1
+ import { useDefineAppContext } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import { useWardPatientGrouping } from '../../hooks/useWardPatientGrouping';
4
+ import { type MaternalWardViewContext, type WardViewContext } from '../../types';
5
+ import WardViewHeader from '../../ward-view-header/ward-view-header.component';
6
+ import Ward from '../ward.component';
7
+ import MaternalWardBeds from './maternal-ward-beds.component';
8
+ import MaternalWardPatientCardHeader from './maternal-ward-patient-card-header.component';
9
+ import MaternalWardPendingPatients from './maternal-ward-pending-patients.component';
10
+ import MaternalWardUnassignedPatients from './maternal-ward-unassigned-patients.component';
11
+ import { useMotherChildrenRelationshipsByPatient } from './maternal-ward-view.resource';
12
+
13
+ const MaternalWardView = () => {
14
+ const wardPatientGroupDetails = useWardPatientGrouping();
15
+ useDefineAppContext<WardViewContext>('ward-view-context', {
16
+ wardPatientGroupDetails,
17
+ WardPatientHeader: MaternalWardPatientCardHeader,
18
+ });
19
+ const { allWardPatientUuids, isLoading } = wardPatientGroupDetails;
20
+
21
+ const motherChildRelationships = useMotherChildrenRelationshipsByPatient(Array.from(allWardPatientUuids), !isLoading);
22
+ useDefineAppContext<MaternalWardViewContext>('maternal-ward-view-context', {
23
+ motherChildRelationships,
24
+ });
25
+
26
+ const wardBeds = <MaternalWardBeds {...motherChildRelationships} />;
27
+ const wardUnassignedPatients = <MaternalWardUnassignedPatients />;
28
+ const wardPendingPatients = <MaternalWardPendingPatients />;
29
+
30
+ return (
31
+ <>
32
+ <WardViewHeader {...{ wardPendingPatients }} />
33
+ <Ward {...{ wardBeds, wardUnassignedPatients }} />
34
+ </>
35
+ );
36
+ };
37
+
38
+ export default MaternalWardView;
@@ -0,0 +1,89 @@
1
+ import { showNotification } from '@openmrs/esm-framework';
2
+ import { useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { useMotherAndChildren, type MothersAndChildrenSearchCriteria } from '../../hooks/useMotherAndChildren';
5
+ import { type PatientAndAdmission } from '../../types';
6
+
7
+ const motherAndChildrenRep =
8
+ 'custom:(childAdmission,mother:(person,identifiers:full,uuid),child:(person,identifiers:full,uuid),motherAdmission)';
9
+
10
+ export function useMotherChildrenRelationshipsByPatient(allWardPatientUuids: string[], fetch: boolean) {
11
+ const { t } = useTranslation();
12
+
13
+ const getChildrenRequestParams: MothersAndChildrenSearchCriteria = {
14
+ mothers: allWardPatientUuids,
15
+ requireMotherHasActiveVisit: true,
16
+ requireChildHasActiveVisit: true,
17
+ requireChildBornDuringMothersActiveVisit: true,
18
+ };
19
+
20
+ const getMotherRequestParams: MothersAndChildrenSearchCriteria = {
21
+ children: allWardPatientUuids,
22
+ requireMotherHasActiveVisit: true,
23
+ requireChildHasActiveVisit: true,
24
+ requireChildBornDuringMothersActiveVisit: true,
25
+ };
26
+
27
+ const {
28
+ data: childrenData,
29
+ isLoading: isLoadingChildrenData,
30
+ error: childrenDataError,
31
+ } = useMotherAndChildren(getChildrenRequestParams, fetch && allWardPatientUuids.length > 0, motherAndChildrenRep);
32
+ const {
33
+ data: motherData,
34
+ isLoading: isLoadingMotherData,
35
+ error: motherDataError,
36
+ } = useMotherAndChildren(getMotherRequestParams, fetch && allWardPatientUuids.length > 0, motherAndChildrenRep);
37
+
38
+ if (childrenDataError) {
39
+ showNotification({
40
+ title: t('errorLoadingChildren', 'Error loading children info'),
41
+ kind: 'error',
42
+ critical: true,
43
+ description: childrenDataError?.message,
44
+ });
45
+ }
46
+ if (motherDataError) {
47
+ showNotification({
48
+ title: t('errorLoadingMother', 'Error loading mother info'),
49
+ kind: 'error',
50
+ critical: true,
51
+ description: motherDataError?.message,
52
+ });
53
+ }
54
+
55
+ const relationships = useMemo(() => {
56
+ if (isLoadingChildrenData || isLoadingMotherData) {
57
+ return null;
58
+ }
59
+
60
+ const motherByChildUuid = new Map<string, PatientAndAdmission>();
61
+ const childrenByMotherUuid = new Map<string, PatientAndAdmission[]>();
62
+
63
+ for (const { child, childAdmission, mother, motherAdmission } of motherData ?? []) {
64
+ motherByChildUuid.set(child.uuid, { patient: mother, currentAdmission: motherAdmission });
65
+ if (!childrenByMotherUuid.has(mother.uuid)) {
66
+ childrenByMotherUuid.set(mother.uuid, []);
67
+ }
68
+ childrenByMotherUuid.get(mother.uuid).push({ patient: child, currentAdmission: childAdmission });
69
+ }
70
+
71
+ for (const { child, childAdmission, mother, motherAdmission } of childrenData ?? []) {
72
+ motherByChildUuid.set(child.uuid, { patient: mother, currentAdmission: motherAdmission });
73
+ if (!childrenByMotherUuid.has(mother.uuid)) {
74
+ childrenByMotherUuid.set(mother.uuid, []);
75
+ }
76
+
77
+ // careful, we need to avoid duplicate entries if both mother and child as in same ward
78
+ const children = childrenByMotherUuid.get(mother.uuid);
79
+ const hasChildAlready = children.some(({ patient }) => patient.uuid == child.uuid);
80
+ if (!hasChildAlready) {
81
+ children.push({ patient: child, currentAdmission: childAdmission });
82
+ }
83
+ }
84
+
85
+ return { motherByChildUuid, childrenByMotherUuid };
86
+ }, [childrenData, motherData, isLoadingChildrenData, isLoadingMotherData]);
87
+
88
+ return relationships;
89
+ }
@@ -1,23 +1,20 @@
1
- import React, { useMemo } from 'react';
2
1
  import { InlineNotification } from '@carbon/react';
2
+ import { ExtensionSlot, useFeatureFlag } from '@openmrs/esm-framework';
3
+ import classNames from 'classnames';
4
+ import React from 'react';
3
5
  import { useTranslation } from 'react-i18next';
4
- import { WorkspaceContainer, useFeatureFlag } from '@openmrs/esm-framework';
5
- import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
6
- import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
7
- import WardBed from './ward-bed.component';
8
- import { bedLayoutToBed, filterBeds } from './ward-view.resource';
9
- 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
6
  import useWardLocation from '../hooks/useWardLocation';
14
- import UnassignedPatient from '../beds/unassigned-patient.component';
7
+ import { useWardConfig } from './ward-view.resource';
8
+ import styles from './ward-view.scss';
15
9
 
16
- const WardView = () => {
10
+ const WardView: React.FC<{}> = () => {
17
11
  const response = useWardLocation();
18
- const { isLoadingLocation, invalidLocation } = response;
12
+ const { isLoadingLocation, invalidLocation, location } = response;
19
13
  const { t } = useTranslation();
20
- const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
14
+
15
+ const locationUuid = location?.uuid;
16
+ const isVertical = useFeatureFlag('ward-view-vertical-tiling');
17
+ const wardConfig = useWardConfig(locationUuid);
21
18
 
22
19
  if (isLoadingLocation) {
23
20
  return <></>;
@@ -27,157 +24,12 @@ const WardView = () => {
27
24
  return <InlineNotification kind="error" title={t('invalidLocationSpecified', 'Invalid location specified')} />;
28
25
  }
29
26
 
30
- return (
31
- <div className={styles.wardView}>
32
- <WardViewHeader />
33
- <div className={styles.wardViewMain}>
34
- {isBedManagementModuleInstalled ? <WardViewWithBedManagement /> : <WardViewWithoutBedManagement />}
35
- </div>
36
- <WorkspaceContainer overlay contextKey="ward" />
37
- </div>
38
- );
39
- };
40
-
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();
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]);
54
-
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
- });
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
-
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 })
127
- }
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
- };
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();
27
+ const wardId = wardConfig.id;
146
28
 
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
29
  return (
174
- <>
175
- {Array(20)
176
- .fill(0)
177
- .map((_, i) => (
178
- <EmptyBedSkeleton key={i} />
179
- ))}
180
- </>
30
+ <div className={classNames(styles.wardView, { [styles.verticalTiling]: isVertical })}>
31
+ <ExtensionSlot name={wardId} />
32
+ </div>
181
33
  );
182
34
  };
183
35
 
@@ -1,4 +1,26 @@
1
- import type { AdmissionLocationFetchResponse, Bed, BedLayout } from '../types';
1
+ import { showNotification, useConfig, type Patient } from '@openmrs/esm-framework';
2
+ import type { TFunction } from 'i18next';
3
+ import { useMemo } from 'react';
4
+ import {
5
+ type PendingItemsElementConfig,
6
+ type ColoredObsTagsElementConfig,
7
+ type IdentifierElementConfig,
8
+ type ObsElementConfig,
9
+ type PatientAddressElementConfig,
10
+ type WardConfigObject,
11
+ type WardDefinition,
12
+ type AdmissionRequestNoteElementConfig,
13
+ } from '../config-schema';
14
+ import type {
15
+ AdmissionLocationFetchResponse,
16
+ Bed,
17
+ BedLayout,
18
+ InpatientAdmission,
19
+ InpatientRequest,
20
+ WardMetrics,
21
+ WardPatientGroupDetails,
22
+ } from '../types';
23
+ import { useTranslation } from 'react-i18next';
2
24
 
3
25
  // the server side has 2 slightly incompatible types for Bed
4
26
  export function bedLayoutToBed(bedLayout: BedLayout): Bed {
@@ -20,5 +42,175 @@ export function filterBeds(admissionLocation: AdmissionLocationFetchResponse): B
20
42
  const bedLayouts = admissionLocation.bedLayouts
21
43
  .filter((bl) => bl.bedId)
22
44
  .sort((bedA, bedB) => collator.compare(bedA.bedNumber, bedB.bedNumber));
45
+
23
46
  return bedLayouts;
24
47
  }
48
+
49
+ export function getWardMetrics(bedLayouts: BedLayout[], wardPatientGroup: WardPatientGroupDetails): WardMetrics {
50
+ const bedMetrics = {
51
+ patients: '--',
52
+ freeBeds: '--',
53
+ capacity: '--',
54
+ };
55
+ if (bedLayouts == null || bedLayouts.length == 0) return bedMetrics;
56
+ const total = bedLayouts.length;
57
+ const occupiedBeds = bedLayouts.filter((bed) => bed.patients.length > 0);
58
+ const patients = occupiedBeds.length;
59
+ const freeBeds = total - patients;
60
+ const capacity = total != 0 ? Math.trunc((wardPatientGroup.totalPatientsCount / total) * 100) : 0;
61
+ return {
62
+ patients: wardPatientGroup?.totalPatientsCount.toString() ?? '--',
63
+ freeBeds: freeBeds.toString(),
64
+ capacity: capacity.toString(),
65
+ };
66
+ }
67
+
68
+ export function getInpatientAdmissionsUuidMap(inpatientAdmissions: InpatientAdmission[]) {
69
+ const map = new Map<string, InpatientAdmission>();
70
+ for (const inpatientAdmission of inpatientAdmissions ?? []) {
71
+ // TODO: inpatientAdmission is undefined sometimes, why?
72
+ if (inpatientAdmission) {
73
+ map.set(inpatientAdmission.patient.uuid, inpatientAdmission);
74
+ }
75
+ }
76
+ return map;
77
+ }
78
+
79
+ export function createAndGetWardPatientGrouping(
80
+ inpatientAdmissions: InpatientAdmission[],
81
+ admissionLocation: AdmissionLocationFetchResponse,
82
+ inpatientRequests: InpatientRequest[],
83
+ ) {
84
+ const inpatientAdmissionsByPatientUuid = getInpatientAdmissionsUuidMap(inpatientAdmissions);
85
+
86
+ const wardAdmittedPatientsWithBed = new Map<string, InpatientAdmission>();
87
+ const wardUnadmittedPatientsWithBed = new Map<string, Patient>();
88
+ const bedLayouts = admissionLocation && filterBeds(admissionLocation);
89
+ const allWardPatientUuids = new Set<string>();
90
+ let wardPatientPendingCount = 0;
91
+ bedLayouts?.map((bedLayout) => {
92
+ const { patients } = bedLayout;
93
+ patients.map((patient) => {
94
+ const patientAdmittedWithBed = inpatientAdmissionsByPatientUuid.get(patient.uuid);
95
+ allWardPatientUuids.add(patient.uuid);
96
+ if (patientAdmittedWithBed) {
97
+ wardAdmittedPatientsWithBed.set(patient.uuid, patientAdmittedWithBed);
98
+ //count the pending metric
99
+ const dispositionType = patientAdmittedWithBed.currentInpatientRequest?.dispositionType;
100
+ if (dispositionType == 'TRANSFER' || dispositionType == 'DISCHARGE') wardPatientPendingCount++;
101
+ } else {
102
+ wardUnadmittedPatientsWithBed.set(patient.uuid, patient);
103
+ }
104
+ });
105
+ });
106
+
107
+ const wardUnassignedPatientsList =
108
+ inpatientAdmissions?.filter((inpatientAdmission) => {
109
+ allWardPatientUuids.add(inpatientAdmission.patient.uuid);
110
+ return (
111
+ !wardAdmittedPatientsWithBed.has(inpatientAdmission.patient.uuid) &&
112
+ !wardUnadmittedPatientsWithBed.has(inpatientAdmission.patient.uuid)
113
+ );
114
+ }) ?? [];
115
+
116
+ //excluding inpatientRequests
117
+ const totalPatientsCount = allWardPatientUuids.size;
118
+
119
+ for (const inpatientRequest of inpatientRequests ?? []) {
120
+ // TODO: inpatientRequest is undefined sometimes, why?
121
+ if (inpatientRequest) {
122
+ allWardPatientUuids.add(inpatientRequest.patient.uuid);
123
+ }
124
+ }
125
+
126
+ return {
127
+ wardAdmittedPatientsWithBed,
128
+ wardUnadmittedPatientsWithBed,
129
+ wardPatientPendingCount,
130
+ bedLayouts,
131
+ wardUnassignedPatientsList,
132
+ allWardPatientUuids,
133
+ totalPatientsCount,
134
+ };
135
+ }
136
+
137
+ export function getWardMetricNameTranslation(name: string, t: TFunction) {
138
+ switch (name) {
139
+ case 'patients':
140
+ return t('patients', 'Patients');
141
+ case 'freeBeds':
142
+ return t('freeBeds', 'Free beds');
143
+ case 'capacity':
144
+ return t('capacity', 'Capacity');
145
+ case 'pendingOut':
146
+ return t('pendingOut', 'Pending out');
147
+ }
148
+ }
149
+
150
+ export function getWardMetricValueTranslation(name: string, t: TFunction, value: string) {
151
+ switch (name) {
152
+ case 'patients':
153
+ return t('patientsMetricValue', '{{ metricValue }}', { metricValue: value });
154
+ case 'freeBeds':
155
+ return t('freeBedsMetricValue', '{{ metricValue }}', { metricValue: value });
156
+ case 'capacity':
157
+ return t('capacityMetricValue', '{{ metricValue }} %', { metricValue: value });
158
+ case 'pendingOut':
159
+ return t('pendingOutMetricValue', '{{ metricValue }}', { metricValue: value });
160
+ }
161
+ }
162
+
163
+ export function useElementConfig(elementType: 'obs', id: string): ObsElementConfig;
164
+ export function useElementConfig(elementType: 'patientIdentifier', id: string): IdentifierElementConfig;
165
+ export function useElementConfig(elementType: 'patientAddress', id: string): PatientAddressElementConfig;
166
+ export function useElementConfig(elementType: 'coloredObsTags', id: string): ColoredObsTagsElementConfig;
167
+ export function useElementConfig(elementType: 'pendingItems', id: string): PendingItemsElementConfig;
168
+ export function useElementConfig(elementType: 'admissionRequestNote', id: string): AdmissionRequestNoteElementConfig;
169
+ export function useElementConfig(elementType, id: string): object {
170
+ const config = useConfig<WardConfigObject>();
171
+ const { t } = useTranslation();
172
+
173
+ try {
174
+ return config?.patientCardElements?.[elementType]?.find((elementConfig) => elementConfig?.id == id);
175
+ } catch (e) {
176
+ showNotification({
177
+ title: t('errorConfiguringPatientCard', 'Error configuring patient card'),
178
+ kind: 'error',
179
+ critical: true,
180
+ description: t(
181
+ 'errorConfiguringPatientCardMessage',
182
+ 'Unable to find configuration for {{elementType}}, id: {{id}}',
183
+ {
184
+ elementType,
185
+ id,
186
+ },
187
+ ),
188
+ });
189
+ return null;
190
+ }
191
+ }
192
+
193
+ export function useWardConfig(locationUuid: string): WardDefinition {
194
+ const { wards } = useConfig<WardConfigObject>();
195
+
196
+ const currentWardConfig = useMemo(() => {
197
+ const cardDefinition = wards?.find((wardDef) => {
198
+ return (
199
+ wardDef.appliedTo == null ||
200
+ wardDef.appliedTo?.length == 0 ||
201
+ wardDef.appliedTo.some((criteria) => criteria.location == locationUuid)
202
+ );
203
+ });
204
+
205
+ return cardDefinition;
206
+ }, [wards, locationUuid]);
207
+
208
+ if (!currentWardConfig) {
209
+ console.warn(
210
+ 'No ward card configuration has `appliedTo` criteria that matches the current location. Using the default configuration.',
211
+ );
212
+ return { id: 'default-ward' };
213
+ }
214
+
215
+ return currentWardConfig;
216
+ }