@openmrs/esm-ward-app 7.0.1-pre.60 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.turbo/turbo-build.log +23 -27
  2. package/dist/130.js +1 -1
  3. package/dist/130.js.map +1 -1
  4. package/dist/152.js +1 -0
  5. package/dist/152.js.map +1 -0
  6. package/dist/255.js +2 -0
  7. package/dist/{697.js.LICENSE.txt → 255.js.LICENSE.txt} +0 -6
  8. package/dist/255.js.map +1 -0
  9. package/dist/303.js +1 -0
  10. package/dist/303.js.map +1 -0
  11. package/dist/331.js +1 -0
  12. package/dist/331.js.map +1 -0
  13. package/dist/430.js +1 -0
  14. package/dist/430.js.map +1 -0
  15. package/dist/574.js +1 -1
  16. package/dist/747.js +1 -0
  17. package/dist/747.js.map +1 -0
  18. package/dist/836.js +2 -0
  19. package/dist/836.js.LICENSE.txt +5 -0
  20. package/dist/836.js.map +1 -0
  21. package/dist/main.js +1 -1
  22. package/dist/main.js.map +1 -1
  23. package/dist/openmrs-esm-ward-app.js +1 -1
  24. package/dist/openmrs-esm-ward-app.js.buildmanifest.json +190 -46
  25. package/dist/openmrs-esm-ward-app.js.map +1 -1
  26. package/dist/routes.json +1 -1
  27. package/package.json +2 -3
  28. package/src/beds/occupied-bed.component.tsx +7 -12
  29. package/src/beds/occupied-bed.scss +1 -1
  30. package/src/beds/occupied-bed.test.tsx +14 -5
  31. package/src/config-schema.ts +173 -7
  32. package/src/constant.ts +1 -0
  33. package/src/createDashboardLink.component.tsx +42 -0
  34. package/src/hooks/useAdmissionLocation.ts +4 -5
  35. package/src/hooks/useAdmittedPatients.ts +13 -0
  36. package/src/hooks/useConcept.ts +11 -0
  37. package/src/hooks/useInpatientRequest.ts +15 -0
  38. package/src/hooks/useLocation.test.ts +38 -0
  39. package/src/hooks/useLocation.ts +9 -0
  40. package/src/hooks/useObs.ts +21 -0
  41. package/src/hooks/useWardLocation.test.ts +78 -0
  42. package/src/hooks/useWardLocation.ts +26 -0
  43. package/src/index.ts +24 -3
  44. package/src/root.component.tsx +2 -1
  45. package/src/routes.json +37 -7
  46. package/src/types/index.ts +40 -2
  47. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +3 -1
  48. package/src/ward-patient-card/row-elements/ward-patient-bed-number.tsx +3 -0
  49. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +80 -0
  50. package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +3 -1
  51. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +52 -0
  52. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +58 -0
  53. package/src/ward-patient-card/ward-patient-card-row.resources.tsx +12 -5
  54. package/src/ward-patient-card/ward-patient-card.scss +43 -0
  55. package/src/ward-patient-card/ward-patient-card.tsx +12 -2
  56. package/src/ward-patient-workspace/ward-patient-action-button.extension.tsx +18 -0
  57. package/src/ward-patient-workspace/ward-patient.style.scss +11 -0
  58. package/src/ward-patient-workspace/ward-patient.workspace.tsx +79 -0
  59. package/src/ward-view/ward-bed.component.tsx +6 -9
  60. package/src/ward-view/ward-view.component.tsx +80 -39
  61. package/src/ward-view/ward-view.scss +0 -15
  62. package/src/ward-view/ward-view.test.tsx +38 -25
  63. package/src/ward-view-header/admission-requests-bar.component.tsx +50 -0
  64. package/src/ward-view-header/admission-requests-bar.test.tsx +43 -0
  65. package/src/ward-view-header/admission-requests.scss +33 -0
  66. package/src/ward-view-header/ward-view-header.component.tsx +18 -0
  67. package/src/ward-view-header/ward-view-header.scss +8 -0
  68. package/src/ward-workspace/admission-request-card.component.tsx +23 -0
  69. package/src/ward-workspace/admission-request-card.scss +34 -0
  70. package/src/ward-workspace/admission-request-workspace.test.tsx +32 -0
  71. package/src/ward-workspace/admission-requests-workspace.component.tsx +21 -0
  72. package/src/ward-workspace/admission-requests-workspace.scss +13 -0
  73. package/translations/en.json +6 -1
  74. package/dist/49.js +0 -1
  75. package/dist/49.js.map +0 -1
  76. package/dist/697.js +0 -2
  77. package/dist/697.js.map +0 -1
@@ -1,68 +1,100 @@
1
+ import React, { useMemo } from 'react';
1
2
  import { InlineNotification } from '@carbon/react';
2
- import { useFeatureFlag, useLocations, useSession, type Location } from '@openmrs/esm-framework';
3
- import React from 'react';
4
3
  import { useTranslation } from 'react-i18next';
5
- import { useParams } from 'react-router-dom';
4
+ import { WorkspaceContainer, useFeatureFlag } from '@openmrs/esm-framework';
6
5
  import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
7
6
  import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
8
7
  import WardBed from './ward-bed.component';
9
8
  import { bedLayoutToBed, filterBeds } from './ward-view.resource';
10
9
  import styles from './ward-view.scss';
10
+ import WardViewHeader from '../ward-view-header/ward-view-header.component';
11
+ import { type AdmittedPatient, type WardPatient } from '../types';
12
+ import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
13
+ import useWardLocation from '../hooks/useWardLocation';
11
14
 
12
15
  const WardView = () => {
13
- const { locationUuid: locationUuidFromUrl } = useParams();
14
- const { sessionLocation } = useSession();
15
- const allLocations = useLocations();
16
+ const response = useWardLocation();
17
+ const { isLoadingLocation, errorFetchingLocation, invalidLocation } = response;
18
+
16
19
  const { t } = useTranslation();
17
20
  const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
18
- const locationFromUrl = allLocations.find((l) => l.uuid === locationUuidFromUrl);
19
- const invalidLocation = Boolean(locationUuidFromUrl && !locationFromUrl);
20
- const location = (locationFromUrl ?? sessionLocation) as any as Location;
21
+
21
22
  //TODO:Display patients with admitted status (based on their observations) that have no beds assigned
22
- if (!isBedManagementModuleInstalled) {
23
+ if (!isBedManagementModuleInstalled || isLoadingLocation) {
23
24
  return <></>;
24
25
  }
25
26
 
27
+ if (invalidLocation) {
28
+ return <InlineNotification kind="error" title={t('invalidLocationSpecified', 'Invalid location specified')} />;
29
+ }
30
+
26
31
  return (
27
32
  <div className={styles.wardView}>
28
- <div className={styles.wardViewHeader}>
29
- <div className={styles.wardViewHeaderLocationDisplay}>
30
- <h4>{location?.display}</h4>
31
- </div>
32
- <div className={styles.wardViewHeaderAdmissionRequestMenuBar}>{/* TODO: Admission Request bar */}</div>
33
- </div>
33
+ <WardViewHeader />
34
34
  <div className={styles.wardViewMain}>
35
- {invalidLocation ? (
36
- <InlineNotification
37
- kind="error"
38
- lowContrast={true}
39
- title={t('invalidLocationSpecified', 'Invalid location specified')}
40
- subtitle={t('unknownLocationUuid', 'Unknown location uuid: {{locationUuidFromUrl}}', {
41
- locationUuidFromUrl,
42
- })}
43
- />
44
- ) : (
45
- <WardViewByLocation location={location} />
46
- )}
35
+ <WardViewByLocation />
47
36
  </div>
37
+ <WorkspaceContainer overlay contextKey="ward" />
48
38
  </div>
49
39
  );
50
40
  };
51
41
 
52
- const WardViewByLocation = ({ location }: { location: Location }) => {
53
- const { admissionLocation, isLoading, error } = useAdmissionLocation(location.uuid);
42
+ const WardViewByLocation = () => {
43
+ const { location } = useWardLocation();
44
+ const {
45
+ admissionLocation,
46
+ isLoading: isLoadingLocation,
47
+ error: errorLoadingLocation,
48
+ } = useAdmissionLocation(location?.uuid);
49
+ const {
50
+ admittedPatients,
51
+ isLoading: isLoadingPatients,
52
+ error: errorLoadingPatients,
53
+ } = useAdmittedPatients(location.uuid);
54
54
  const { t } = useTranslation();
55
+ const admittedPatientsByUuid = useMemo(() => {
56
+ const map = new Map<string, AdmittedPatient>();
57
+ for (const admittedPatient of admittedPatients ?? []) {
58
+ map.set(admittedPatient.patient.uuid, admittedPatient);
59
+ }
60
+ return map;
61
+ }, [admittedPatients]);
55
62
 
56
- if (admissionLocation) {
63
+ if (admissionLocation != null && admittedPatients != null) {
57
64
  const bedLayouts = filterBeds(admissionLocation);
58
65
 
66
+ const wardBeds = bedLayouts.map((bedLayout, i) => {
67
+ const { patients } = bedLayout;
68
+ const bed = bedLayoutToBed(bedLayout);
69
+ const wardPatients: WardPatient[] = patients.map((patient) => {
70
+ const admittedPatient = admittedPatientsByUuid.get(patient.uuid);
71
+
72
+ if (admittedPatient) {
73
+ // ideally, we can just use the patient object within admittedPatient
74
+ // and not need the one from bedLayouts, however, the emr api
75
+ // does not respect custom representation right now and does not return
76
+ // all required fields for the patient object
77
+ // TODO: change after this is done. https://openmrs.atlassian.net/browse/EA-192
78
+ return { ...admittedPatient, patient, admitted: true };
79
+ }
80
+
81
+ // patient assigned a bed but *not* admitted
82
+ // TODO: get the patient's visit and current location
83
+ return {
84
+ patient,
85
+ visit: null,
86
+ admitted: true,
87
+ currentLocation: null,
88
+ timeSinceAdmissionInMinutes: null,
89
+ timeAtInpatientLocationInMinutes: null,
90
+ };
91
+ });
92
+ return <WardBed key={bed.uuid} bed={bed} wardPatients={wardPatients} />;
93
+ });
94
+
59
95
  return (
60
96
  <>
61
- {bedLayouts.map((bedLayout, i) => {
62
- const { patient } = bedLayout;
63
- const bed = bedLayoutToBed(bedLayout);
64
- return <WardBed key={bed.uuid} bed={bed} patients={patient ? [patient] : null} />;
65
- })}
97
+ {wardBeds}
66
98
  {bedLayouts.length == 0 && (
67
99
  <InlineNotification
68
100
  kind="warning"
@@ -72,7 +104,7 @@ const WardViewByLocation = ({ location }: { location: Location }) => {
72
104
  )}
73
105
  </>
74
106
  );
75
- } else if (isLoading) {
107
+ } else if (isLoadingLocation || isLoadingPatients) {
76
108
  return (
77
109
  <>
78
110
  {Array(20)
@@ -82,18 +114,27 @@ const WardViewByLocation = ({ location }: { location: Location }) => {
82
114
  ))}
83
115
  </>
84
116
  );
85
- } else {
117
+ } else if (errorLoadingLocation) {
86
118
  return (
87
119
  <InlineNotification
88
120
  kind="error"
89
121
  lowContrast={true}
90
122
  title={t('errorLoadingWardLocation', 'Error loading ward location')}
91
123
  subtitle={
92
- error?.message ??
124
+ errorLoadingLocation?.message ??
93
125
  t('invalidWardLocation', 'Invalid ward location: {{location}}', { location: location.display })
94
126
  }
95
127
  />
96
128
  );
129
+ } else {
130
+ return (
131
+ <InlineNotification
132
+ kind="error"
133
+ lowContrast={true}
134
+ title={t('errorLoadingPatients', 'Error loading admitted patients')}
135
+ subtitle={errorLoadingPatients?.message}
136
+ />
137
+ );
97
138
  }
98
139
  };
99
140
 
@@ -12,21 +12,6 @@
12
12
  padding: 0 spacing.$spacing-05;
13
13
  }
14
14
 
15
- .wardViewHeader {
16
- display: flex;
17
- margin: spacing.$spacing-05 0;
18
- }
19
-
20
- .wardViewHeaderLocationDisplay {
21
- flex: 1;
22
- }
23
-
24
- .wardViewHeaderAdmissionRequestMenuBar {
25
- background-color: black;
26
- color: white;
27
- padding: 4px 0 4px 12px;
28
- }
29
-
30
15
  .wardViewMain {
31
16
  background-color: #e4e4e4;
32
17
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
@@ -1,21 +1,21 @@
1
+ import React from 'react';
2
+ import { screen } from '@testing-library/react';
1
3
  import {
2
4
  type Person,
3
5
  type ConfigSchema,
4
6
  getDefaultsFromConfigSchema,
5
7
  useConfig,
6
- useSession,
7
8
  useFeatureFlag,
8
9
  } from '@openmrs/esm-framework';
9
- import { screen } from '@testing-library/react';
10
- import React from 'react';
11
10
  import { useParams } from 'react-router-dom';
12
- import { mockLocations } from '../../../../__mocks__/locations.mock';
13
11
  import { mockAdmissionLocation } from '../../../../__mocks__/wards.mock';
14
12
  import { renderWithSwr } from '../../../../tools/test-utils';
15
13
  import { configSchema } from '../config-schema';
16
14
  import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
17
- import WardView from './ward-view.component';
18
15
  import { mockPatientAlice } from '../../../../__mocks__/patient.mock';
16
+ import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
17
+ import useWardLocation from '../hooks/useWardLocation';
18
+ import WardView from './ward-view.component';
19
19
 
20
20
  jest.replaceProperty(mockPatientAlice.person as Person, 'preferredName', {
21
21
  uuid: '',
@@ -27,21 +27,18 @@ jest.mocked(useConfig).mockReturnValue({
27
27
  ...getDefaultsFromConfigSchema<ConfigSchema>(configSchema),
28
28
  });
29
29
 
30
- const mockedSessionLocation = { uuid: 'abcd', display: 'mock location', links: [] };
31
- jest.mocked(useSession).mockReturnValue({
32
- sessionLocation: mockedSessionLocation,
33
- authenticated: true,
34
- sessionId: 'sessionId',
35
- });
36
-
37
30
  const mockedUseFeatureFlag = useFeatureFlag as jest.Mock;
38
31
 
39
- jest.mock('@openmrs/esm-framework', () => {
40
- return {
41
- ...jest.requireActual('@openmrs/esm-framework'),
42
- useLocations: jest.fn().mockImplementation(() => mockLocations.data.results),
43
- };
44
- });
32
+ jest.mock('../hooks/useWardLocation', () =>
33
+ jest.fn().mockReturnValue({
34
+ location: { uuid: 'abcd', display: 'mock location' },
35
+ isLoadingLocation: false,
36
+ errorFetchingLocation: null,
37
+ invalidLocation: false,
38
+ }),
39
+ );
40
+
41
+ const mockedUseWardLocation = useWardLocation as jest.Mock;
45
42
 
46
43
  jest.mock('react-router-dom', () => ({
47
44
  ...jest.requireActual('react-router-dom'),
@@ -52,6 +49,10 @@ const mockedUseParams = useParams as jest.Mock;
52
49
  jest.mock('../hooks/useAdmissionLocation', () => ({
53
50
  useAdmissionLocation: jest.fn(),
54
51
  }));
52
+ jest.mock('../hooks/useAdmittedPatients', () => ({
53
+ useAdmittedPatients: jest.fn(),
54
+ }));
55
+
55
56
  jest.mocked(useAdmissionLocation).mockReturnValue({
56
57
  error: undefined,
57
58
  mutate: jest.fn(),
@@ -59,19 +60,25 @@ jest.mocked(useAdmissionLocation).mockReturnValue({
59
60
  isLoading: false,
60
61
  admissionLocation: mockAdmissionLocation,
61
62
  });
63
+ jest.mocked(useAdmittedPatients).mockReturnValue({
64
+ error: undefined,
65
+ mutate: jest.fn(),
66
+ isValidating: false,
67
+ isLoading: false,
68
+ admittedPatients: [],
69
+ });
62
70
 
63
71
  describe('WardView:', () => {
64
72
  it('renders the session location when no location provided in URL', () => {
65
73
  renderWithSwr(<WardView />);
66
- const header = screen.getByRole('heading', { name: mockedSessionLocation.display });
74
+ const header = screen.getByRole('heading', { name: 'mock location' });
67
75
  expect(header).toBeInTheDocument();
68
76
  });
69
77
 
70
78
  it('renders the location provided in URL', () => {
71
- const locationToUse = mockLocations.data.results[0];
72
- mockedUseParams.mockReturnValueOnce({ locationUuid: locationToUse.uuid });
79
+ mockedUseParams.mockReturnValueOnce({ locationUuid: 'abcd' });
73
80
  renderWithSwr(<WardView />);
74
- const header = screen.getByRole('heading', { name: locationToUse.display });
81
+ const header = screen.getByRole('heading', { name: 'mock location' });
75
82
  expect(header).toBeInTheDocument();
76
83
  });
77
84
 
@@ -82,17 +89,23 @@ describe('WardView:', () => {
82
89
  });
83
90
 
84
91
  it('renders notification for invalid location uuid', () => {
85
- mockedUseParams.mockReturnValueOnce({ locationUuid: 'invalid-uuid' });
92
+ mockedUseWardLocation.mockReturnValueOnce({
93
+ location: null,
94
+ isLoadingLocation: false,
95
+ errorFetchingLocation: null,
96
+ invalidLocation: true,
97
+ });
98
+
86
99
  renderWithSwr(<WardView />);
87
100
  const notification = screen.getByRole('status');
88
101
  expect(notification).toBeInTheDocument();
89
- const invalidText = screen.getByText('Unknown location uuid: invalid-uuid');
102
+ const invalidText = screen.getByText('Invalid location specified');
90
103
  expect(invalidText).toBeInTheDocument();
91
104
  });
92
105
 
93
106
  it('screen should be empty if backend module is not installed', () => {
94
107
  mockedUseFeatureFlag.mockReturnValueOnce(false);
95
108
  const { container } = renderWithSwr(<WardView />);
96
- expect(container.firstChild).not.toBeInTheDocument();
109
+ expect(container).toBeEmptyDOMElement();
97
110
  });
98
111
  });
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { Movement } from '@carbon/react/icons';
3
+ import { Button, InlineNotification } from '@carbon/react';
4
+ import { ArrowRightIcon, isDesktop, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
5
+ import { useInpatientRequest } from '../hooks/useInpatientRequest';
6
+ import { useTranslation } from 'react-i18next';
7
+ import useWardLocation from '../hooks/useWardLocation';
8
+ import styles from './admission-requests.scss';
9
+
10
+ const AdmissionRequestsBar = () => {
11
+ const { location } = useWardLocation();
12
+ const { inpatientRequests, isLoading, error } = useInpatientRequest(location?.uuid);
13
+ const admissionRequests = inpatientRequests?.filter((request) => request.type == 'ADMISSION');
14
+ const { t } = useTranslation();
15
+ const layout = useLayoutType();
16
+
17
+ if (isLoading || !admissionRequests?.length) {
18
+ return null;
19
+ }
20
+
21
+ if (error) {
22
+ console.error(error);
23
+ return (
24
+ <InlineNotification
25
+ kind="error"
26
+ title={t('errorLoadingPatientAdmissionRequests', 'Error Loading patient admission requests')}
27
+ />
28
+ );
29
+ }
30
+
31
+ return (
32
+ <div className={styles.admissionRequestsContainer}>
33
+ <Movement className={styles.movementIcon} size="24" />
34
+ <span className={styles.content}>
35
+ {t('admissionRequestsCount', '{{count}} admission requests', {
36
+ count: admissionRequests.length,
37
+ })}
38
+ </span>
39
+ <Button
40
+ onClick={() => launchWorkspace('admission-requests-workspace', { admissionRequests })}
41
+ renderIcon={ArrowRightIcon}
42
+ kind="ghost"
43
+ size={isDesktop(layout) ? 'sm' : 'lg'}>
44
+ {t('manage', 'Manage')}
45
+ </Button>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ export default AdmissionRequestsBar;
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { renderWithSwr } from '../../../../tools/test-utils';
4
+ import { screen } from '@testing-library/react';
5
+ import { launchWorkspace } from '@openmrs/esm-framework';
6
+ import AdmissionRequestsBar from './admission-requests-bar.component';
7
+ import { mockInpatientRequest } from '../../../../__mocks__/ward-patient';
8
+ import { useInpatientRequest } from '../hooks/useInpatientRequest';
9
+
10
+ jest.mock('@openmrs/esm-framework', () => {
11
+ return {
12
+ ...jest.requireActual('@openmrs/esm-framework'),
13
+ launchWorkspace: jest.fn(),
14
+ };
15
+ });
16
+
17
+ jest.mock('../hooks/useInpatientRequest', () => ({
18
+ useInpatientRequest: jest.fn(),
19
+ }));
20
+ const mockInpatientRequestResponse = {
21
+ error: undefined,
22
+ mutate: jest.fn(),
23
+ isValidating: false,
24
+ isLoading: false,
25
+ inpatientRequests: [mockInpatientRequest],
26
+ };
27
+ jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
28
+
29
+ describe('Admission Requests Button', () => {
30
+ it('call launch workspace when clicked on manage button', async () => {
31
+ const user = userEvent.setup();
32
+ renderWithSwr(<AdmissionRequestsBar />);
33
+
34
+ await user.click(screen.getByRole('button', { name: /manage/i }));
35
+ expect(launchWorkspace).toHaveBeenCalled();
36
+ });
37
+
38
+ it('there should be one admission request', () => {
39
+ renderWithSwr(<AdmissionRequestsBar />);
40
+
41
+ expect(screen.getByText('1 admission requests')).toBeInTheDocument();
42
+ });
43
+ });
@@ -0,0 +1,33 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .admissionRequestsContainer {
6
+ background-color: $color-gray-70;
7
+ display: flex;
8
+ align-items: center;
9
+ padding: spacing.$spacing-02 0 spacing.$spacing-02 spacing.$spacing-04;
10
+ background-color: #393939;
11
+
12
+ & > button {
13
+ color: #78a9ff;
14
+
15
+ svg {
16
+ fill: #78a9ff !important;
17
+ }
18
+ }
19
+ }
20
+
21
+ .movementIcon {
22
+ padding: spacing.$spacing-02;
23
+ border-radius: 50%;
24
+ fill: $ui-03;
25
+ background-color: $color-blue-60-2;
26
+ margin-right: spacing.$spacing-03;
27
+ }
28
+
29
+ .content {
30
+ @include type.type-style('heading-compact-01');
31
+ color: $ui-02;
32
+ margin-right: spacing.$spacing-03;
33
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import styles from './ward-view-header.scss';
3
+ import AdmissionRequestsBar from './admission-requests-bar.component';
4
+ import useWardLocation from '../hooks/useWardLocation';
5
+
6
+ interface WardViewHeaderProps {}
7
+
8
+ const WardViewHeader: React.FC<WardViewHeaderProps> = () => {
9
+ const { location } = useWardLocation();
10
+ return (
11
+ <div className={styles.wardViewHeader}>
12
+ <h4>{location?.display}</h4>
13
+ <AdmissionRequestsBar />
14
+ </div>
15
+ );
16
+ };
17
+
18
+ export default WardViewHeader;
@@ -0,0 +1,8 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+
3
+ .wardViewHeader {
4
+ margin: spacing.$spacing-05 0;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: space-between;
8
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import styles from './admission-request-card.scss';
3
+ import { useParams } from 'react-router-dom';
4
+ import { type Patient } from '@openmrs/esm-framework';
5
+ import { usePatientCardRows } from '../ward-patient-card/ward-patient-card-row.resources';
6
+
7
+ interface AdmissionRequestCardProps {
8
+ patient: Patient;
9
+ }
10
+
11
+ const AdmissionRequestCard: React.FC<AdmissionRequestCardProps> = ({ patient }) => {
12
+ const { locationUuid } = useParams();
13
+ const admissionPatientCardSlots = usePatientCardRows(locationUuid);
14
+ return (
15
+ <div className={styles.admissionRequestCardHeader}>
16
+ {admissionPatientCardSlots.map((AdmissionPatientCard, i) => (
17
+ <AdmissionPatientCard key={i} patient={patient} bed={null} visit={null} />
18
+ ))}
19
+ </div>
20
+ );
21
+ };
22
+
23
+ export default AdmissionRequestCard;
@@ -0,0 +1,34 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .admissionRequestCardHeader {
6
+ background-color: $ui-03;
7
+ padding: spacing.$spacing-03;
8
+ border: 1px solid $color-gray-30;
9
+ height: fit-content;
10
+ color: $color-gray-70;
11
+ display: flex;
12
+ align-items: center;
13
+ flex-wrap: wrap;
14
+ > div:not(:first-of-type)::before {
15
+ content: '·';
16
+ padding: spacing.$spacing-02;
17
+ }
18
+ }
19
+
20
+ .patientName,
21
+ .patientGender,
22
+ .patientAge {
23
+ @include type.type-style('heading-01');
24
+ }
25
+
26
+ .patientName {
27
+ color: $color-gray-100;
28
+ }
29
+
30
+ .time,
31
+ .city,
32
+ .ward {
33
+ @include type.type-style('body-compact-01');
34
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { screen } from '@testing-library/react';
3
+ import AdmissionRequestsWorkspace from './admission-requests-workspace.component';
4
+ import { type ConfigSchema, type Person, getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
5
+ import { renderWithSwr } from 'tools';
6
+ import { mockInpatientRequest } from '__mocks__';
7
+ import { configSchema } from '../config-schema';
8
+
9
+ jest.replaceProperty(mockInpatientRequest.patient.person as Person, 'preferredName', {
10
+ uuid: '',
11
+ givenName: 'Alice',
12
+ familyName: 'Johnson',
13
+ });
14
+
15
+ jest.mocked(useConfig).mockReturnValue({
16
+ ...getDefaultsFromConfigSchema<ConfigSchema>(configSchema),
17
+ });
18
+
19
+ jest.mock('@openmrs/esm-framework', () => {
20
+ return {
21
+ ...jest.requireActual('@openmrs/esm-framework'),
22
+ closeWorkspace: jest.fn(),
23
+ };
24
+ });
25
+
26
+ describe('Admission Requests Workspace', () => {
27
+ it('should render a admission request card', () => {
28
+ renderWithSwr(<AdmissionRequestsWorkspace admissionRequests={[mockInpatientRequest]} />);
29
+ const { givenName, familyName } = mockInpatientRequest.patient.person!.preferredName!;
30
+ expect(screen.getByText(givenName + ' ' + familyName)).toBeInTheDocument();
31
+ });
32
+ });
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import styles from './admission-requests-workspace.scss';
3
+ import AdmissionRequestCard from './admission-request-card.component';
4
+ import { type InpatientRequest } from '../types';
5
+
6
+ interface AdmissionRequestsWorkspaceProps {
7
+ admissionRequests: InpatientRequest[];
8
+ }
9
+ const AdmissionRequestsWorkspace: React.FC<AdmissionRequestsWorkspaceProps> = ({ admissionRequests }) => {
10
+ return (
11
+ <div className={styles.admissionRequestsWorkspaceContainer}>
12
+ <div className={styles.admissionRequestsWorkspace}>
13
+ {admissionRequests.map((admissionRequest) => (
14
+ <AdmissionRequestCard patient={admissionRequest.patient} />
15
+ ))}
16
+ </div>
17
+ </div>
18
+ );
19
+ };
20
+
21
+ export default AdmissionRequestsWorkspace;
@@ -0,0 +1,13 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .admissionRequestsWorkspaceContainer {
6
+ min-height: var(--desktop-workspace-window-height);
7
+ }
8
+ .admissionRequestsWorkspace {
9
+ padding: spacing.$spacing-04;
10
+ display: grid;
11
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
12
+ gap: 1rem;
13
+ }
@@ -1,9 +1,14 @@
1
1
  {
2
+ "admissionRequestsCount_one": "{{count}} admission request",
3
+ "admissionRequestsCount_other": "{{count}} admission requests",
2
4
  "bedShare": "Bed share",
3
5
  "emptyBed": "Empty bed",
6
+ "errorLoadingPatientAdmissionRequests": "Error Loading patient admission requests",
7
+ "errorLoadingPatients": "Error loading admitted patients",
4
8
  "errorLoadingWardLocation": "Error loading ward location",
5
9
  "invalidLocationSpecified": "Invalid location specified",
6
10
  "invalidWardLocation": "Invalid ward location: {{location}}",
11
+ "manage": "Manage",
7
12
  "noBedsConfigured": "No beds configured for this location",
8
- "unknownLocationUuid": "Unknown location uuid: {{locationUuidFromUrl}}"
13
+ "wards": "Wards"
9
14
  }