@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
@@ -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/styles/scss/spacing';
2
+ @use '@carbon/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .metric {
6
+ margin-left: spacing.$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: 15px;
24
+ width: 15px;
25
+ }
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import styles from './ward-metrics.scss';
3
+ import { useBeds } from '../hooks/useBeds';
4
+ import { showNotification, useAppContext, useFeatureFlag } from '@openmrs/esm-framework';
5
+ import { useTranslation } from 'react-i18next';
6
+ import {
7
+ getWardMetricNameTranslation,
8
+ getWardMetrics,
9
+ getWardMetricValueTranslation,
10
+ } from '../ward-view/ward-view.resource';
11
+ import WardMetric from './ward-metric.component';
12
+ import type { WardPatientGroupDetails } from '../types';
13
+ import useWardLocation from '../hooks/useWardLocation';
14
+
15
+ const wardMetrics = [{ name: 'patients' }, { name: 'freeBeds' }, { name: 'capacity' }];
16
+
17
+ const WardMetrics = () => {
18
+ const { location } = useWardLocation();
19
+ const { t } = useTranslation();
20
+ const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
21
+ const wardPatientGroup = useAppContext<WardPatientGroupDetails>('ward-patients-group');
22
+ const { admissionLocationResponse, inpatientAdmissionResponse, inpatientRequestResponse, bedLayouts } =
23
+ wardPatientGroup || {};
24
+ const { isLoading, error } = admissionLocationResponse ?? {};
25
+ const isDataLoading =
26
+ admissionLocationResponse?.isLoading ||
27
+ inpatientAdmissionResponse?.isLoading ||
28
+ inpatientRequestResponse?.isLoading;
29
+ if (!wardPatientGroup) return <></>;
30
+
31
+ if (error) {
32
+ showNotification({
33
+ kind: 'error',
34
+ title: t('errorLoadingBedDetails', 'Error loading bed details'),
35
+ description: error.message,
36
+ });
37
+ }
38
+
39
+ const wardMetricValues = getWardMetrics(bedLayouts, wardPatientGroup);
40
+ return (
41
+ <div className={styles.metricsContainer}>
42
+ {isBedManagementModuleInstalled ? (
43
+ wardMetrics.map((wardMetric) => {
44
+ return (
45
+ <WardMetric
46
+ metricName={getWardMetricNameTranslation(wardMetric.name, t)}
47
+ metricValue={getWardMetricValueTranslation(wardMetric.name, t, wardMetricValues[wardMetric.name])}
48
+ isLoading={!!isLoading || !!isDataLoading}
49
+ key={wardMetric.name}
50
+ />
51
+ );
52
+ })
53
+ ) : (
54
+ <WardMetric
55
+ metricName={getWardMetricNameTranslation('patients', t)}
56
+ metricValue={'--'}
57
+ isLoading={false}
58
+ key={'patients'}
59
+ />
60
+ )}
61
+ {isBedManagementModuleInstalled && (
62
+ <WardMetric
63
+ metricName={getWardMetricNameTranslation('pendingOut', t)}
64
+ metricValue={
65
+ error
66
+ ? '--'
67
+ : getWardMetricValueTranslation('pendingOut', t, wardPatientGroup?.wardPatientPendingCount?.toString())
68
+ }
69
+ isLoading={!!isDataLoading}
70
+ key="pending"
71
+ />
72
+ )}
73
+ </div>
74
+ );
75
+ };
76
+
77
+ export default WardMetrics;
@@ -0,0 +1,8 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
3
+
4
+ .metricsContainer {
5
+ display: flex;
6
+ align-items: end;
7
+ margin-left: auto;
8
+ }
@@ -0,0 +1,91 @@
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
+
16
+ const wardMetrics = [
17
+ { name: 'patients', key: 'patients', defaultTranslation: 'Patients' },
18
+ { name: 'freeBeds', key: 'freeBeds', defaultTranslation: 'Free beds' },
19
+ { name: 'capacity', key: 'capacity', defaultTranslation: 'Capacity' },
20
+ ];
21
+
22
+ jest.mock('react-router-dom', () => ({
23
+ ...jest.requireActual('react-router-dom'),
24
+ useParams: jest.fn().mockReturnValue({}),
25
+ }));
26
+
27
+ jest.mock('../hooks/useWardLocation', () =>
28
+ jest.fn().mockReturnValue({
29
+ location: { uuid: 'abcd', display: 'mock location' },
30
+ isLoadingLocation: false,
31
+ errorFetchingLocation: null,
32
+ invalidLocation: false,
33
+ }),
34
+ );
35
+
36
+ jest.mock('../hooks/useBeds', () => ({
37
+ useBeds: jest.fn(),
38
+ }));
39
+
40
+ jest.mock('../hooks/useAdmissionLocation', () => ({
41
+ useAdmissionLocation: jest.fn(),
42
+ }));
43
+ jest.mock('../hooks/useInpatientAdmission', () => ({
44
+ useInpatientAdmission: jest.fn(),
45
+ }));
46
+
47
+ jest.mock('../hooks/useInpatientRequest', () => ({
48
+ useInpatientRequest: jest.fn(),
49
+ }));
50
+
51
+ const mockUseWardLocation = jest.mocked(useWardLocation);
52
+
53
+ const mockAdmissionLocationResponse = jest.mocked(useAdmissionLocation).mockReturnValue({
54
+ error: undefined,
55
+ mutate: jest.fn(),
56
+ isValidating: false,
57
+ isLoading: false,
58
+ admissionLocation: mockAdmissionLocation,
59
+ });
60
+ const mockInpatientAdmissionResponse = jest.mocked(useInpatientAdmission).mockReturnValue({
61
+ error: undefined,
62
+ mutate: jest.fn(),
63
+ isValidating: false,
64
+ isLoading: false,
65
+ inpatientAdmissions: mockInpatientAdmissions,
66
+ });
67
+
68
+ const inpatientAdmissionsUuidMap = getInpatientAdmissionsUuidMap(mockInpatientAdmissions);
69
+ const mockWardPatientGroupDetails = {
70
+ admissionLocationResponse: mockAdmissionLocationResponse(),
71
+ inpatientAdmissionResponse: mockInpatientAdmissionResponse(),
72
+ ...createAndGetWardPatientGrouping(mockInpatientAdmissions, mockAdmissionLocation, mockInpatientRequest),
73
+ };
74
+ jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails);
75
+ describe('Ward Metrics', () => {
76
+ it('Should display metrics of in the ward ', () => {
77
+ mockUseWardLocation.mockReturnValueOnce({
78
+ location: null,
79
+ isLoadingLocation: false,
80
+ errorFetchingLocation: null,
81
+ invalidLocation: true,
82
+ });
83
+ const { bedLayouts } = mockWardPatientGroupDetails;
84
+ const bedMetrics = getWardMetrics(bedLayouts, mockWardPatientGroupDetails);
85
+ renderWithSwr(<WardMetrics />);
86
+ for (let [key, value] of Object.entries(bedMetrics)) {
87
+ const fieldName = wardMetrics.find((metric) => metric.name == key)?.defaultTranslation;
88
+ expect(screen.getByText(fieldName!)).toBeInTheDocument();
89
+ }
90
+ });
91
+ });
@@ -2,14 +2,17 @@ import React 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
7
  interface WardViewHeaderProps {}
7
8
 
8
9
  const WardViewHeader: React.FC<WardViewHeaderProps> = () => {
9
10
  const { location } = useWardLocation();
11
+
10
12
  return (
11
13
  <div className={styles.wardViewHeader}>
12
14
  <h4>{location?.display}</h4>
15
+ <WardMetrics />
13
16
  <AdmissionRequestsBar />
14
17
  </div>
15
18
  );
@@ -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
  }
@@ -2,11 +2,12 @@ import { Button } from '@carbon/react';
2
2
  import { ArrowRightIcon, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
3
3
  import React, { useCallback } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
- import type { WardPatientCard } from '../../types';
5
+ import type { WardPatientWorkspaceProps, WardPatientCard } from '../../types';
6
6
  import type { AdmitPatientFormWorkspaceProps } from '../admit-patient-form-workspace/types';
7
7
  import styles from './admission-request-card.scss';
8
8
 
9
- const AdmissionRequestCardActions: WardPatientCard = ({ patient, inpatientRequest }) => {
9
+ const AdmissionRequestCardActions: WardPatientCard = (wardPatient) => {
10
+ const { patient, inpatientRequest } = wardPatient;
10
11
  const { dispositionType } = inpatientRequest;
11
12
  const { t } = useTranslation();
12
13
  const responsiveSize = useLayoutType() === 'tablet' ? 'lg' : 'md';
@@ -14,9 +15,16 @@ const AdmissionRequestCardActions: WardPatientCard = ({ patient, inpatientReques
14
15
  () => launchWorkspace<AdmitPatientFormWorkspaceProps>('admit-patient-form-workspace', { patient, dispositionType }),
15
16
  [],
16
17
  );
18
+
19
+ const launchPatientTransferForm = useCallback(() => {
20
+ launchWorkspace<WardPatientWorkspaceProps>('patient-transfer-request-workspace', {
21
+ wardPatient,
22
+ });
23
+ }, [wardPatient]);
24
+
17
25
  return (
18
26
  <div className={styles.admissionRequestActionBar}>
19
- <Button kind="ghost" size={responsiveSize}>
27
+ <Button kind="ghost" size={responsiveSize} onClick={launchPatientTransferForm}>
20
28
  {t('transferElsewhere', 'Transfer elsewhere')}
21
29
  </Button>
22
30
  <Button kind="ghost" renderIcon={ArrowRightIcon} size={responsiveSize} onClick={launchPatientAdmissionForm}>
@@ -1,12 +1,11 @@
1
1
  import { ExtensionSlot, formatDatetime, getLocale } from '@openmrs/esm-framework';
2
2
  import classNames from 'classnames';
3
3
  import React from 'react';
4
- import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
5
- import styles from './admission-request-card.scss';
6
- import type WardPatientCard from '../../ward-patient-card/ward-patient-card.component';
7
4
  import { useCurrentWardCardConfig } from '../../hooks/useCurrentWardCardConfig';
8
- import { WardPatientCardElement } from '../../ward-patient-card/ward-patient-card-element.component';
9
5
  import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-name';
6
+ import { WardPatientCardElement } from '../../ward-patient-card/ward-patient-card-element.component';
7
+ import type WardPatientCard from '../../ward-patient-card/ward-patient-card.component';
8
+ import styles from './admission-request-card.scss';
10
9
 
11
10
  const AdmissionRequestCardHeader: WardPatientCard = (wardPatient) => {
12
11
  const { inpatientRequest } = wardPatient;
@@ -42,7 +41,7 @@ const AdmissionRequestCardHeader: WardPatientCard = (wardPatient) => {
42
41
  <ExtensionSlot
43
42
  name={rowsExtensionSlotName}
44
43
  state={extensionSlotState}
45
- className={styles.admissionRequestCardRow}
44
+ className={styles.admissionRequestCardExtensionSlot}
46
45
  />
47
46
  </div>
48
47
  );
@@ -25,10 +25,14 @@
25
25
  }
26
26
  }
27
27
 
28
- .admissionRequestCardRow {
29
- padding: layout.$spacing-05;
30
- margin: layout.$spacing-03;
31
- background-color: white;
28
+ .admissionRequestCardExtensionSlot {
29
+ display: none;
30
+
31
+ &:has(div:not(:empty)) {
32
+ display: block;
33
+ margin: layout.$spacing-03 0;
34
+ background-color: white;
35
+ }
32
36
  }
33
37
 
34
38
  .admissionEncounterDetails {
@@ -23,12 +23,15 @@ mockUseWardLocation.mockReturnValue({
23
23
  invalidLocation: false,
24
24
  });
25
25
 
26
- const mockInpatientRequestResponse = {
26
+ const mockInpatientRequestResponse: ReturnType<typeof useInpatientRequest> = {
27
27
  error: undefined,
28
28
  mutate: jest.fn(),
29
29
  isValidating: false,
30
30
  isLoading: false,
31
- inpatientRequests: [mockInpatientRequest],
31
+ inpatientRequests: mockInpatientRequest,
32
+ totalCount: 1,
33
+ hasMore: false,
34
+ loadMore: jest.fn(),
32
35
  };
33
36
 
34
37
  jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
@@ -43,6 +46,8 @@ const workspaceProps = {
43
46
  describe('Admission Requests Workspace', () => {
44
47
  it('should render a admission request card', () => {
45
48
  renderWithSwr(<AdmissionRequestsWorkspace {...workspaceProps} />);
46
- expect(screen.getByText(mockInpatientRequest.patient.person?.preferredName?.display as string)).toBeInTheDocument();
49
+ expect(
50
+ screen.getByText(mockInpatientRequest[0].patient.person?.preferredName?.display as string),
51
+ ).toBeInTheDocument();
47
52
  });
48
53
  });
@@ -9,6 +9,8 @@ import { type InpatientRequest } from '../../types';
9
9
 
10
10
  interface AdmissionRequestsWorkspaceProps {}
11
11
  const AdmissionRequestsWorkspace: React.FC<AdmissionRequestsWorkspaceProps> = () => {
12
+ // note: useAppContext() does not work here for some reason, so we call `useInpatientRequest`
13
+ // directly. See: https://openmrs.atlassian.net/browse/O3-4020
12
14
  const {
13
15
  inpatientRequests,
14
16
  isLoading: isLoadingInpatientRequests,
@@ -1,21 +1,17 @@
1
- import React from 'react';
1
+ import { openmrsFetch, showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework';
2
2
  import { screen } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
+ import React from 'react';
5
+ import { mockAdmissionLocation, mockLocationInpatientWard, mockPatientAlice } from '../../../../../__mocks__';
4
6
  import { renderWithSwr } from '../../../../../tools';
5
- import AdmitPatientFormWorkspace from './admit-patient-form.workspace';
6
- import {
7
- mockAdmissionLocation,
8
- mockInpatientRequest,
9
- mockLocationInpatientWard,
10
- mockPatientAlice,
11
- } from '../../../../../__mocks__';
12
- import type { DispositionType } from '../../types';
13
- import type { AdmitPatientFormWorkspaceProps } from './types';
7
+ import { mockWardPatientGroupDetails } from '../../../mock';
14
8
  import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
15
- import { openmrsFetch, provide, showSnackbar, useFeatureFlag, useSession } from '@openmrs/esm-framework';
16
9
  import useEmrConfiguration from '../../hooks/useEmrConfiguration';
17
- import useWardLocation from '../../hooks/useWardLocation';
18
10
  import { useInpatientRequest } from '../../hooks/useInpatientRequest';
11
+ import useWardLocation from '../../hooks/useWardLocation';
12
+ import type { DispositionType } from '../../types';
13
+ import AdmitPatientFormWorkspace from './admit-patient-form.workspace';
14
+ import type { AdmitPatientFormWorkspaceProps } from './types';
19
15
 
20
16
  jest.mock('../../hooks/useAdmissionLocation', () => ({
21
17
  useAdmissionLocation: jest.fn(),
@@ -29,15 +25,23 @@ jest.mock('../../hooks/useInpatientRequest', () => ({
29
25
  useInpatientRequest: jest.fn(),
30
26
  }));
31
27
 
32
- const mockedUseInpatientRequest = jest.mocked(useInpatientRequest);
28
+ jest.mock('../../hooks/useWardPatientGrouping', () => ({
29
+ useWardPatientGrouping: jest.fn(),
30
+ }));
31
+
32
+ jest.mock('../../hooks/useInpatientAdmission', () => ({
33
+ useInpatientAdmission: jest.fn(),
34
+ }));
35
+
33
36
  const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration);
34
37
  const mockedUseWardLocation = jest.mocked(useWardLocation);
35
38
  const mockedOpenmrsFetch = jest.mocked(openmrsFetch);
36
- const mockedUseAdmissionLocation = jest.mocked(useAdmissionLocation);
37
39
  const mockedUseFeatureFlag = jest.mocked(useFeatureFlag);
38
40
  const mockedShowSnackbar = jest.mocked(showSnackbar);
39
41
  const mockedUseSession = jest.mocked(useSession);
40
42
 
43
+ jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails());
44
+
41
45
  const mockWorkspaceProps: AdmitPatientFormWorkspaceProps = {
42
46
  patient: mockPatientAlice,
43
47
  closeWorkspace: jest.fn(),
@@ -45,24 +49,19 @@ const mockWorkspaceProps: AdmitPatientFormWorkspaceProps = {
45
49
  promptBeforeClosing: jest.fn(),
46
50
  setTitle: jest.fn(),
47
51
  dispositionType: 'ADMIT',
52
+ setCancelTitle: jest.fn(),
53
+ setCancelMessage: jest.fn(),
54
+ setCancelConfirmText: jest.fn(),
48
55
  };
49
56
 
50
57
  function renderAdmissionForm(dispositionType: DispositionType = 'ADMIT') {
51
58
  renderWithSwr(<AdmitPatientFormWorkspace {...{ ...mockWorkspaceProps, dispositionType }} />);
52
59
  }
53
60
 
54
- const mockedMutateInpatientRequest = jest.fn();
55
-
56
61
  describe('Testing AdmitPatientForm', () => {
57
62
  beforeEach(() => {
58
63
  jest.clearAllMocks();
59
- mockedUseAdmissionLocation.mockReturnValue({
60
- isLoading: false,
61
- isValidating: false,
62
- admissionLocation: mockAdmissionLocation,
63
- mutate: jest.fn(),
64
- error: undefined,
65
- });
64
+
66
65
  mockedUseSession.mockReturnValue({
67
66
  currentProvider: {
68
67
  uuid: 'current-provider-uuid',
@@ -91,13 +90,6 @@ describe('Testing AdmitPatientForm', () => {
91
90
  },
92
91
  mutateEmrConfiguration: jest.fn(),
93
92
  });
94
- mockedUseInpatientRequest.mockReturnValue({
95
- mutate: mockedMutateInpatientRequest,
96
- error: undefined,
97
- inpatientRequests: [mockInpatientRequest],
98
- isLoading: false,
99
- isValidating: false,
100
- });
101
93
  mockedUseWardLocation.mockReturnValue({
102
94
  location: mockLocationInpatientWard,
103
95
  invalidLocation: false,
@@ -157,30 +149,14 @@ describe('Testing AdmitPatientForm', () => {
157
149
 
158
150
  it('should render admit patient form if bed management module is present, but no beds are configured', () => {
159
151
  mockedUseFeatureFlag.mockReturnValue(true);
160
- mockedUseAdmissionLocation.mockReturnValueOnce({
161
- isLoading: false,
162
- isValidating: false,
163
- admissionLocation: {
164
- ...mockAdmissionLocation,
165
- totalBeds: 0,
166
- bedLayouts: [],
167
- },
168
- mutate: jest.fn(),
169
- error: null,
170
- });
152
+ const replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
171
153
  renderAdmissionForm();
172
154
  expect(screen.getByText('Select a bed')).toBeInTheDocument();
173
- expect(screen.getByText('No beds configured for Inpatient Ward location')).toBeInTheDocument();
155
+ expect(screen.getByText(/No beds configured/i)).toBeInTheDocument();
156
+ replacedProperty.restore();
174
157
  });
175
158
 
176
159
  it('should submit the form, create encounter and submit bed', async () => {
177
- mockedUseAdmissionLocation.mockReturnValueOnce({
178
- isLoading: false,
179
- isValidating: false,
180
- admissionLocation: mockAdmissionLocation,
181
- mutate: jest.fn(),
182
- error: null,
183
- });
184
160
  // @ts-ignore - we only need these two keys for now
185
161
  mockedOpenmrsFetch.mockResolvedValue({
186
162
  ok: true,
@@ -290,17 +266,6 @@ describe('Testing AdmitPatientForm', () => {
290
266
  });
291
267
 
292
268
  it('should admit patient if no beds are configured', async () => {
293
- mockedUseAdmissionLocation.mockReturnValueOnce({
294
- isLoading: false,
295
- isValidating: false,
296
- admissionLocation: {
297
- ...mockAdmissionLocation,
298
- totalBeds: 0,
299
- bedLayouts: [],
300
- },
301
- mutate: jest.fn(),
302
- error: null,
303
- });
304
269
  // @ts-ignore - we only need these two keys for now
305
270
  mockedOpenmrsFetch.mockResolvedValue({
306
271
  ok: true,
@@ -313,7 +278,7 @@ describe('Testing AdmitPatientForm', () => {
313
278
  const admitButton = screen.getByRole('button', { name: 'Admit' });
314
279
  expect(admitButton).toBeEnabled();
315
280
  await user.click(admitButton);
316
- expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(1);
281
+ expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(2);
317
282
  expect(mockedOpenmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/encounter', {
318
283
  method: 'POST',
319
284
  headers: {
@@ -332,6 +297,9 @@ describe('Testing AdmitPatientForm', () => {
332
297
  ],
333
298
  },
334
299
  });
300
+ expect(mockedOpenmrsFetch).toHaveBeenCalledWith(`/ws/rest/v1/beds/1?patientUuid=${mockPatientAlice.uuid}`, {
301
+ method: 'DELETE',
302
+ });
335
303
  expect(mockedShowSnackbar).toHaveBeenCalledWith({
336
304
  kind: 'success',
337
305
  subtitle: 'Patient admitted successfully to Inpatient Ward',
@@ -1,19 +1,16 @@
1
+ import { Button, ButtonSet, Column, Dropdown, DropdownSkeleton, Form, InlineNotification, Row } from '@carbon/react';
2
+ import { zodResolver } from '@hookform/resolvers/zod';
3
+ import { showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework';
1
4
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { z } from 'zod';
3
5
  import { Controller, useForm } from 'react-hook-form';
4
- import { zodResolver } from '@hookform/resolvers/zod';
5
6
  import { useTranslation } from 'react-i18next';
6
- import { Button, ButtonSet, Column, Dropdown, DropdownSkeleton, Form, InlineNotification, Row } from '@carbon/react';
7
- import { showSnackbar, useFeatureFlag, useSession } from '@openmrs/esm-framework';
8
- import { filterBeds } from '../../ward-view/ward-view.resource';
9
- import type { BedLayout, DispositionType } from '../../types';
10
- import { assignPatientToBed, createEncounter } from '../../ward.resource';
11
- import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
12
- import { useInpatientRequest } from '../../hooks/useInpatientRequest';
7
+ import { z } from 'zod';
13
8
  import useEmrConfiguration from '../../hooks/useEmrConfiguration';
14
9
  import useWardLocation from '../../hooks/useWardLocation';
15
- import type { AdmitPatientFormWorkspaceProps } from './types';
10
+ import type { BedLayout, WardPatientGroupDetails } from '../../types';
11
+ import { assignPatientToBed, createEncounter, removePatientFromBed } from '../../ward.resource';
16
12
  import styles from './admit-patient-form.scss';
13
+ import type { AdmitPatientFormWorkspaceProps } from './types';
17
14
 
18
15
  const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
19
16
  patient,
@@ -26,11 +23,13 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
26
23
  const { location } = useWardLocation();
27
24
  const { currentProvider } = useSession();
28
25
  const [isSubmitting, setIsSubmitting] = useState(false);
29
- const { mutate: mutateInpatientRequest } = useInpatientRequest();
30
26
  const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
31
27
  const [showErrorNotifications, setShowErrorNotifications] = useState(false);
32
- const { isLoading, admissionLocation, mutate: mutateAdmissionLocation } = useAdmissionLocation();
33
- const beds = useMemo(() => (isLoading ? [] : filterBeds(admissionLocation)), [admissionLocation]);
28
+ const wardPatientGrouping = useAppContext<WardPatientGroupDetails>('ward-patients-group');
29
+ const { isLoading, mutate: mutateAdmissionLocation } = wardPatientGrouping?.admissionLocationResponse ?? {};
30
+ const { mutate: mutateInpatientRequest } = wardPatientGrouping?.inpatientRequestResponse ?? {};
31
+ const { mutate: mutateInpatientAdmission } = wardPatientGrouping?.inpatientAdmissionResponse ?? {};
32
+ const beds = isLoading ? [] : wardPatientGrouping?.bedLayouts ?? [];
34
33
  const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
35
34
  const getBedRepresentation = useCallback((bedLayout: BedLayout) => {
36
35
  const bedNumber = bedLayout.bedNumber;
@@ -92,8 +91,15 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
92
91
  if (response.ok) {
93
92
  if (bedSelected) {
94
93
  return assignPatientToBed(values.bedId, patient.uuid, response.data.uuid);
94
+ } else {
95
+ const bed = wardPatientGrouping.bedLayouts.find((bedLayout) =>
96
+ bedLayout.patients.some((p) => p.uuid == patient.uuid),
97
+ );
98
+ if (bed) {
99
+ return removePatientFromBed(bed.bedId, patient.uuid);
100
+ }
101
+ return response;
95
102
  }
96
- return response;
97
103
  }
98
104
  },
99
105
  (err: Error) => {
@@ -134,9 +140,6 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
134
140
  }),
135
141
  });
136
142
  }
137
- mutateAdmissionLocation();
138
- mutateInpatientRequest();
139
- closeWorkspaceWithSavedChanges();
140
143
  }
141
144
  },
142
145
  () => {
@@ -148,16 +151,28 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
148
151
  'Patient admitted successfully but fail to assign bed to patient',
149
152
  ),
150
153
  });
151
- mutateAdmissionLocation();
152
- mutateInpatientRequest();
153
- closeWorkspaceWithSavedChanges();
154
154
  },
155
155
  )
156
156
  .finally(() => {
157
157
  setIsSubmitting(false);
158
+ mutateAdmissionLocation();
159
+ mutateInpatientRequest();
160
+ mutateInpatientAdmission();
161
+ closeWorkspaceWithSavedChanges();
158
162
  });
159
163
  },
160
- [beds, patient, emrConfiguration, location, closeWorkspaceWithSavedChanges, dispositionType, currentProvider],
164
+ [
165
+ beds,
166
+ patient,
167
+ emrConfiguration,
168
+ location,
169
+ closeWorkspaceWithSavedChanges,
170
+ dispositionType,
171
+ currentProvider,
172
+ mutateAdmissionLocation,
173
+ mutateInpatientRequest,
174
+ mutateInpatientAdmission,
175
+ ],
161
176
  );
162
177
 
163
178
  const onError = useCallback((values) => {
@@ -165,6 +180,7 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
165
180
  setIsSubmitting(false);
166
181
  }, []);
167
182
 
183
+ if (!wardPatientGrouping) return <></>;
168
184
  return (
169
185
  <Form control={control} className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
170
186
  <div className={styles.formContent}>
@@ -8,10 +8,10 @@ import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-n
8
8
 
9
9
  const WardPatientWorkspaceBanner = (wardPatient: WardPatient) => {
10
10
  const { headerRowElements } = useCurrentWardCardConfig();
11
- const { patient, bed, visit } = wardPatient;
11
+ const { patient, bed } = wardPatient;
12
12
 
13
- if (!(patient && visit)) {
14
- console.warn('Patient details and visit details were not received by the workspace');
13
+ if (!patient) {
14
+ console.warn('Patient details were not received by the ward workspace');
15
15
  return null;
16
16
  }
17
17