@kenyaemr/esm-ward-app 7.0.3-pre.88 → 7.0.3-pre.94

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 (146) hide show
  1. package/.turbo/turbo-build.log +24 -16
  2. package/dist/130.js +1 -1
  3. package/dist/130.js.map +1 -1
  4. package/dist/169.js +1 -0
  5. package/dist/169.js.map +1 -0
  6. package/dist/269.js +1 -0
  7. package/dist/269.js.map +1 -0
  8. package/dist/346.js +1 -0
  9. package/dist/346.js.map +1 -0
  10. package/dist/348.js +1 -0
  11. package/dist/348.js.map +1 -0
  12. package/dist/466.js +1 -0
  13. package/dist/466.js.map +1 -0
  14. package/dist/501.js +1 -0
  15. package/dist/501.js.map +1 -0
  16. package/dist/574.js +1 -1
  17. package/dist/577.js +1 -0
  18. package/dist/577.js.map +1 -0
  19. package/dist/659.js +1 -0
  20. package/dist/659.js.map +1 -0
  21. package/dist/749.js +1 -0
  22. package/dist/749.js.map +1 -0
  23. package/dist/76.js +1 -0
  24. package/dist/76.js.map +1 -0
  25. package/dist/767.js +1 -0
  26. package/dist/767.js.map +1 -0
  27. package/dist/793.js +2 -0
  28. package/dist/793.js.map +1 -0
  29. package/dist/803.js +1 -0
  30. package/dist/803.js.map +1 -0
  31. package/dist/940.js +1 -0
  32. package/dist/940.js.map +1 -0
  33. package/dist/960.js +1 -0
  34. package/dist/960.js.map +1 -0
  35. package/dist/kenyaemr-esm-ward-app.js +1 -1
  36. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +330 -42
  37. package/dist/kenyaemr-esm-ward-app.js.map +1 -1
  38. package/dist/main.js +1 -1
  39. package/dist/main.js.map +1 -1
  40. package/dist/routes.json +1 -1
  41. package/package.json +2 -2
  42. package/src/action-menu-buttons/transfer-workspace-siderail.component.tsx +27 -0
  43. package/src/beds/empty-bed.component.tsx +1 -1
  44. package/src/beds/empty-bed.scss +6 -6
  45. package/src/beds/occupied-bed.component.tsx +5 -5
  46. package/src/beds/occupied-bed.scss +2 -3
  47. package/src/beds/occupied-bed.test.tsx +37 -21
  48. package/src/beds/unassigned-patient.component.tsx +20 -0
  49. package/src/beds/unassigned-patient.scss +6 -0
  50. package/src/config-schema-admission-request-note.ts +9 -0
  51. package/src/config-schema-extension-colored-obs-tags.ts +91 -0
  52. package/src/config-schema.ts +165 -231
  53. package/src/createDashboardLink.component.tsx +42 -0
  54. package/src/hooks/useAdmissionLocation.ts +12 -7
  55. package/src/hooks/useCurrentWardCardConfig.ts +32 -0
  56. package/src/hooks/useEmrConfiguration.ts +112 -0
  57. package/src/hooks/useInpatientAdmission.ts +28 -0
  58. package/src/hooks/useInpatientRequest.ts +39 -9
  59. package/src/hooks/useLocation.test.ts +38 -0
  60. package/src/hooks/useLocation.ts +9 -0
  61. package/src/hooks/useLocations.ts +54 -0
  62. package/src/hooks/useMostRecentObs.ts +27 -0
  63. package/src/hooks/useRestPatient.ts +18 -0
  64. package/src/hooks/useWardLocation.test.ts +108 -0
  65. package/src/hooks/useWardLocation.ts +26 -0
  66. package/src/index.ts +71 -4
  67. package/src/location-selector/location-selector.component.tsx +118 -0
  68. package/src/location-selector/location-selector.scss +48 -0
  69. package/src/root.component.tsx +2 -1
  70. package/src/routes.json +79 -12
  71. package/src/types/index.ts +87 -46
  72. package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +27 -0
  73. package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +13 -0
  74. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +7 -13
  75. package/src/ward-patient-card/row-elements/ward-patient-bed-number.tsx +2 -2
  76. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +51 -50
  77. package/src/ward-patient-card/row-elements/ward-patient-gender.component.tsx +27 -0
  78. package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +16 -15
  79. package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +53 -0
  80. package/src/ward-patient-card/row-elements/ward-patient-name.tsx +7 -7
  81. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +4 -4
  82. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +45 -44
  83. package/src/ward-patient-card/row-elements/ward-patient-time-on-ward.tsx +22 -0
  84. package/src/ward-patient-card/row-elements/ward-patient-time-since-admission.tsx +22 -0
  85. package/src/ward-patient-card/ward-patient-card-element.component.tsx +65 -0
  86. package/src/ward-patient-card/ward-patient-card.component.tsx +64 -0
  87. package/src/ward-patient-card/ward-patient-card.scss +61 -12
  88. package/src/ward-patient-workspace/ward-patient-action-button.extension.tsx +18 -0
  89. package/src/ward-patient-workspace/ward-patient.style.scss +11 -0
  90. package/src/ward-patient-workspace/ward-patient.workspace.tsx +51 -0
  91. package/src/ward-view/ward-bed.component.tsx +0 -1
  92. package/src/ward-view/ward-view.component.tsx +114 -76
  93. package/src/ward-view/ward-view.resource.ts +2 -2
  94. package/src/ward-view/ward-view.scss +4 -4
  95. package/src/ward-view/ward-view.test.tsx +76 -49
  96. package/src/ward-view-header/admission-requests-bar.component.tsx +29 -28
  97. package/src/ward-view-header/admission-requests-bar.test.tsx +11 -15
  98. package/src/ward-view-header/admission-requests.scss +20 -25
  99. package/src/ward-view-header/ward-view-header.component.tsx +7 -7
  100. package/src/ward-view-header/ward-view-header.scss +2 -2
  101. package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +29 -0
  102. package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +51 -0
  103. package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +16 -0
  104. package/src/ward-workspace/admission-request-card/admission-request-card.scss +49 -0
  105. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.scss +12 -0
  106. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +48 -0
  107. package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +61 -0
  108. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.scss +35 -0
  109. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +341 -0
  110. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +267 -0
  111. package/src/ward-workspace/admit-patient-form-workspace/types.ts +7 -0
  112. package/src/ward-workspace/patient-banner/patient-banner.component.tsx +29 -0
  113. package/src/ward-workspace/patient-banner/style.scss +23 -0
  114. package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +210 -0
  115. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +238 -0
  116. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +73 -0
  117. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +44 -0
  118. package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +180 -0
  119. package/src/ward-workspace/ward-patient-notes/form/notes-form.scss +30 -0
  120. package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +116 -0
  121. package/src/ward-workspace/ward-patient-notes/history/note.component.tsx +53 -0
  122. package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +55 -0
  123. package/src/ward-workspace/ward-patient-notes/history/notes-container.test.tsx +84 -0
  124. package/src/ward-workspace/ward-patient-notes/history/styles.scss +61 -0
  125. package/src/ward-workspace/ward-patient-notes/notes-action-button.extension.tsx +18 -0
  126. package/src/ward-workspace/ward-patient-notes/notes.resource.ts +71 -0
  127. package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +25 -0
  128. package/src/ward-workspace/ward-patient-notes/types.ts +44 -0
  129. package/src/ward.resource.ts +25 -0
  130. package/translations/en.json +63 -2
  131. package/dist/443.js +0 -1
  132. package/dist/443.js.map +0 -1
  133. package/dist/589.js +0 -1
  134. package/dist/589.js.map +0 -1
  135. package/dist/695.js +0 -2
  136. package/dist/695.js.map +0 -1
  137. package/src/hooks/useAdmittedPatients.ts +0 -13
  138. package/src/ward-patient-card/row-elements/row-elements.scss +0 -16
  139. package/src/ward-patient-card/ward-patient-card-row.resources.tsx +0 -92
  140. package/src/ward-patient-card/ward-patient-card.tsx +0 -20
  141. package/src/ward-workspace/admission-request-card.component.tsx +0 -23
  142. package/src/ward-workspace/admission-request-card.scss +0 -34
  143. package/src/ward-workspace/admission-request-workspace.test.tsx +0 -38
  144. package/src/ward-workspace/admission-requests-workspace.component.tsx +0 -21
  145. package/src/ward-workspace/admission-requests-workspace.scss +0 -13
  146. /package/dist/{695.js.LICENSE.txt → 793.js.LICENSE.txt} +0 -0
@@ -0,0 +1,28 @@
1
+ import useSWR from 'swr';
2
+ import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
3
+ import { type InpatientAdmissionFetchResponse } from '../types';
4
+ import useWardLocation from './useWardLocation';
5
+
6
+ export function useInpatientAdmission() {
7
+ const { location } = useWardLocation();
8
+
9
+ // prettier-ignore
10
+ const customRepresentation =
11
+ 'custom:(visit,' +
12
+ 'patient:(uuid,identifiers,voided,' +
13
+ 'person:(uuid,display,gender,age,birthdate,birthtime,preferredName,preferredAddress,dead,deathDate)),' +
14
+ 'encounterAssigningToCurrentInpatientLocation:(encounterDatetime),' +
15
+ 'firstAdmissionOrTransferEncounter:(encounterDatetime),' +
16
+ ')';
17
+ const { data, ...rest } = useSWR<FetchResponse<InpatientAdmissionFetchResponse>, Error>(
18
+ location
19
+ ? `${restBaseUrl}/emrapi/inpatient/admission?currentInpatientLocation=${location.uuid}&v=${customRepresentation}`
20
+ : null,
21
+ openmrsFetch,
22
+ );
23
+
24
+ return {
25
+ inpatientAdmissions: data?.data?.results,
26
+ ...rest,
27
+ };
28
+ }
@@ -1,13 +1,43 @@
1
- import { openmrsFetch } from '@openmrs/esm-framework';
1
+ import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import type { DispositionType, InpatientRequestFetchResponse } from '../types';
2
3
  import useSWR from 'swr';
3
- import type { InpatientRequest } from '../types';
4
+ import useWardLocation from './useWardLocation';
5
+ import { useMemo } from 'react';
4
6
 
5
- export function useInpatientRequest(locationUuid: string) {
6
- const apiUrl = `/ws/rest/emrapi/inpatient/admissionRequests?admissionLocation=${locationUuid}`;
7
- const { data, ...rest } = useSWR<{ data: Array<InpatientRequest> }, Error>(apiUrl, openmrsFetch);
7
+ // prettier-ignore
8
+ const defaultRep =
9
+ 'custom:(' +
10
+ 'dispositionLocation,' +
11
+ 'dispositionType,' +
12
+ 'disposition,' +
13
+ 'dispositionEncounter:full,' +
14
+ 'patient:(uuid,identifiers,voided,' +
15
+ 'person:(uuid,display,gender,age,birthdate,birthtime,preferredName,preferredAddress,dead,deathDate)),' +
16
+ 'dispositionObsGroup,' +
17
+ 'visit)';
8
18
 
9
- return {
10
- inpatientRequests: data?.data || null,
11
- ...rest,
12
- };
19
+ export function useInpatientRequest(
20
+ dispositionType: Array<DispositionType> = ['ADMIT', 'TRANSFER'],
21
+ rep: string = defaultRep,
22
+ ) {
23
+ const { location } = useWardLocation();
24
+ const searchParams = new URLSearchParams();
25
+ searchParams.set('dispositionType', dispositionType.join(','));
26
+ searchParams.set('dispositionLocation', location?.uuid);
27
+ searchParams.set('v', rep);
28
+
29
+ const { data, ...rest } = useSWR<FetchResponse<InpatientRequestFetchResponse>, Error>(
30
+ location?.uuid ? `${restBaseUrl}/emrapi/inpatient/request?${searchParams.toString()}` : null,
31
+ openmrsFetch,
32
+ );
33
+
34
+ const results = useMemo(
35
+ () => ({
36
+ inpatientRequests: data?.data?.results,
37
+ ...rest,
38
+ }),
39
+ [data, rest],
40
+ );
41
+
42
+ return results;
13
43
  }
@@ -0,0 +1,38 @@
1
+ import useSWRImmutable from 'swr/immutable';
2
+ import { renderHook } from '@testing-library/react';
3
+ import { restBaseUrl } from '@openmrs/esm-framework';
4
+ import useLocation from './useLocation';
5
+
6
+ jest.mock('swr/immutable', () =>
7
+ jest.fn().mockReturnValue({
8
+ data: {},
9
+ error: null,
10
+ isValidating: false,
11
+ mutate: jest.fn(),
12
+ }),
13
+ );
14
+
15
+ const useSWRImmutableMock = useSWRImmutable as jest.Mock;
16
+
17
+ describe('useLocation hook', () => {
18
+ it('should call useLocation', () => {
19
+ const { result } = renderHook(() => useLocation('testUUID'));
20
+ expect(useSWRImmutableMock).toHaveBeenCalledWith(
21
+ `${restBaseUrl}/location/testUUID?v=custom:(display,uuid)`,
22
+ expect.any(Function),
23
+ );
24
+ });
25
+
26
+ it('should call useLocation with the given custom representation', () => {
27
+ const { result } = renderHook(() => useLocation('testUUID', 'custom:(display,uuid,links)'));
28
+ expect(useSWRImmutableMock).toHaveBeenCalledWith(
29
+ `${restBaseUrl}/location/testUUID?v=custom:(display,uuid,links)`,
30
+ expect.any(Function),
31
+ );
32
+ });
33
+
34
+ it('should call useSWR with key=null', () => {
35
+ const { result } = renderHook(() => useLocation(null, 'custom:(display,uuid,links)'));
36
+ expect(useSWRImmutableMock).toHaveBeenCalledWith(null, expect.any(Function));
37
+ });
38
+ });
@@ -0,0 +1,9 @@
1
+ import { type Location, openmrsFetch, restBaseUrl, type FetchResponse } from '@openmrs/esm-framework';
2
+ import useSWRImmutable from 'swr/immutable';
3
+
4
+ export default function useLocation(locationUuid: string, rep: string = 'custom:(display,uuid)') {
5
+ return useSWRImmutable<FetchResponse<Location>>(
6
+ locationUuid ? `${restBaseUrl}/location/${locationUuid}?v=${rep}` : null,
7
+ openmrsFetch,
8
+ );
9
+ }
@@ -0,0 +1,54 @@
1
+ import { type FetchResponse, fhirBaseUrl, openmrsFetch } from '@openmrs/esm-framework';
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import useSWRImmutable from 'swr/immutable';
4
+
5
+ interface FhirLocation {
6
+ fullUrl: string;
7
+ resource: {
8
+ resourceType: 'Location';
9
+ id: string;
10
+ name: string;
11
+ description: string;
12
+ };
13
+ }
14
+
15
+ interface FhirResponse {
16
+ resourceType: 'Bundle';
17
+ id: '6a107c31-d760-4df0-bb70-89ad742225ca';
18
+ meta: {
19
+ lastUpdated: '2024-08-08T06:28:01.495+00:00';
20
+ };
21
+ type: 'searchset';
22
+ total: number;
23
+ link: Array<{
24
+ relation: 'self' | 'prev' | 'next';
25
+ url: string;
26
+ }>;
27
+ entry: Array<FhirLocation>;
28
+ }
29
+
30
+ export default function useLocations(filterCriteria: Array<Array<string>> = [], skip: boolean = false) {
31
+ const [totalLocations, setTotalLocations] = useState(0);
32
+ const [url, setUrl] = useState(`${fhirBaseUrl}/Location`);
33
+ const searchParams = new URLSearchParams(filterCriteria);
34
+ const urlWithSearchParams = `${url}?${searchParams.toString()}`;
35
+ const { data, ...rest } = useSWRImmutable<FetchResponse<FhirResponse>>(
36
+ !skip ? urlWithSearchParams : null,
37
+ openmrsFetch,
38
+ );
39
+
40
+ useEffect(() => {
41
+ if (data?.data) {
42
+ setTotalLocations(data.data.total);
43
+ }
44
+ }, [data]);
45
+
46
+ const results = useMemo(() => {
47
+ return {
48
+ locations: data?.data?.entry?.map((entry) => entry.resource),
49
+ totalLocations,
50
+ ...rest,
51
+ };
52
+ }, [data, rest, totalLocations]);
53
+ return results;
54
+ }
@@ -0,0 +1,27 @@
1
+ import useSWR from 'swr';
2
+ import { type Observation } from '../types';
3
+ import { type Link, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
4
+
5
+ interface ObsSearchCriteria {
6
+ patient: string;
7
+ concept: string;
8
+ }
9
+
10
+ export function useMostRecentObs(criteria?: ObsSearchCriteria, representation = 'default') {
11
+ const params = new URLSearchParams({
12
+ ...criteria,
13
+ v: representation,
14
+ limit: '1',
15
+ });
16
+
17
+ const apiUrl = `${restBaseUrl}/obs?${params}`;
18
+
19
+ const { data, ...rest } = useSWR<
20
+ { data: { results: Array<Observation>; totalCount: number; links: Array<Link> } },
21
+ Error
22
+ >(apiUrl, openmrsFetch);
23
+
24
+ const obs = data?.data?.results?.[0];
25
+
26
+ return { data: obs, ...rest };
27
+ }
@@ -0,0 +1,18 @@
1
+ import { openmrsFetch, restBaseUrl, type FetchResponse, type Patient } from '@openmrs/esm-framework';
2
+ import { useMemo } from 'react';
3
+ import useSWRImmutable from 'swr/immutable';
4
+
5
+ export default function useRestPatient(patientUuid: string) {
6
+ const { data, ...rest } = useSWRImmutable<FetchResponse<Patient>>(
7
+ patientUuid ? `${restBaseUrl}/patient/${patientUuid}` : null,
8
+ openmrsFetch,
9
+ );
10
+ const results = useMemo(
11
+ () => ({
12
+ patient: data?.data,
13
+ ...rest,
14
+ }),
15
+ [data, rest],
16
+ );
17
+ return results;
18
+ }
@@ -0,0 +1,108 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { type FetchResponse, useSession } from '@openmrs/esm-framework';
3
+ import { mockSession } from '__mocks__';
4
+ import { useParams } from 'react-router-dom';
5
+ import useWardLocation from './useWardLocation';
6
+ import useLocation from './useLocation';
7
+
8
+ jest.mock('@openmrs/esm-framework', () => ({
9
+ useSession: jest.fn(),
10
+ }));
11
+ jest.mock('react-router-dom', () => ({
12
+ useParams: jest.fn(),
13
+ }));
14
+ jest.mock('./useLocation', () => jest.fn());
15
+
16
+ const mockUseParams = jest.mocked(useParams);
17
+ const mockUseSession = jest.mocked(useSession);
18
+ const mockUseLocation = useLocation as jest.Mock;
19
+
20
+ describe('useWardLocation', () => {
21
+ beforeEach(() => {
22
+ mockUseSession.mockReturnValue(mockSession.data);
23
+ });
24
+
25
+ it('returns session location when locationUuidFromUrl is not provided', async () => {
26
+ mockUseParams.mockReturnValue({});
27
+ mockUseLocation.mockReturnValue({
28
+ data: null,
29
+ error: null,
30
+ isLoading: null,
31
+ isValidating: null,
32
+ mutate: jest.fn(),
33
+ });
34
+
35
+ const { result } = renderHook(() => useWardLocation());
36
+ expect(result.current.location).toBe(mockSession.data.sessionLocation);
37
+ });
38
+
39
+ it('returns location from useLocation when locationUuidFromUrl is provided', async () => {
40
+ mockUseParams.mockReturnValue({ locationUuid: 'some-location-uuid' });
41
+ mockUseLocation.mockReturnValue({
42
+ data: {
43
+ data: {
44
+ display: 'Test Location',
45
+ name: 'Test Location',
46
+ uuid: 'test-location-uuid',
47
+ },
48
+ },
49
+ error: null,
50
+ isLoading: false,
51
+ isValidating: null,
52
+ mutate: jest.fn(),
53
+ });
54
+
55
+ const { result } = renderHook(() => useWardLocation());
56
+
57
+ expect(result.current.location).toEqual({
58
+ display: 'Test Location',
59
+ name: 'Test Location',
60
+ uuid: 'test-location-uuid',
61
+ });
62
+ expect(result.current.invalidLocation).toBeFalsy();
63
+ });
64
+
65
+ it('handles loading state correctly', async () => {
66
+ mockUseParams.mockReturnValue({ locationUuid: 'uuid' });
67
+ mockUseLocation.mockReturnValue({
68
+ data: null,
69
+ error: null,
70
+ isLoading: true,
71
+ isValidating: false,
72
+ mutate: jest.fn(),
73
+ });
74
+
75
+ const { result } = renderHook(() => useWardLocation());
76
+ expect(result.current.isLoadingLocation).toBe(true);
77
+ });
78
+
79
+ it('handles error state correctly when fetching location fails', async () => {
80
+ const error = new Error('Error fetching location');
81
+ mockUseParams.mockReturnValue({ locationUuid: 'uuid' });
82
+ mockUseLocation.mockReturnValue({
83
+ data: null,
84
+ error,
85
+ isLoading: false,
86
+ isValidating: false,
87
+ mutate: jest.fn(),
88
+ });
89
+
90
+ const { result } = renderHook(() => useWardLocation());
91
+ expect(result.current.errorFetchingLocation).toBe(error);
92
+ });
93
+
94
+ it('identifies invalid location correctly', async () => {
95
+ const error = new Error('Error fetching location');
96
+ mockUseParams.mockReturnValue({ locationUuid: 'uuid' });
97
+ mockUseLocation.mockReturnValue({
98
+ data: null,
99
+ error,
100
+ isLoading: false,
101
+ isValidating: false,
102
+ mutate: jest.fn(),
103
+ });
104
+
105
+ const { result } = renderHook(() => useWardLocation());
106
+ expect(result.current.invalidLocation).toBeTruthy();
107
+ });
108
+ });
@@ -0,0 +1,26 @@
1
+ import { type Location, useSession } from '@openmrs/esm-framework';
2
+ import { useParams } from 'react-router-dom';
3
+ import useLocation from './useLocation';
4
+
5
+ export default function useWardLocation(): {
6
+ location: Location;
7
+ isLoadingLocation: boolean;
8
+ errorFetchingLocation: Error | undefined;
9
+ invalidLocation: boolean;
10
+ } {
11
+ const { locationUuid: locationUuidFromUrl } = useParams();
12
+ const { sessionLocation } = useSession();
13
+ const {
14
+ data: locationResponse,
15
+ isLoading: isLoadingLocation,
16
+ error: errorFetchingLocation,
17
+ } = useLocation(locationUuidFromUrl ? locationUuidFromUrl : null);
18
+ const invalidLocation = locationUuidFromUrl && errorFetchingLocation;
19
+
20
+ return {
21
+ location: locationUuidFromUrl ? locationResponse?.data : sessionLocation,
22
+ isLoadingLocation,
23
+ errorFetchingLocation,
24
+ invalidLocation,
25
+ };
26
+ }
package/src/index.ts CHANGED
@@ -1,11 +1,20 @@
1
- import { defineConfigSchema, getSyncLifecycle, registerBreadcrumbs, registerFeatureFlag } from '@openmrs/esm-framework';
1
+ import {
2
+ defineConfigSchema,
3
+ defineExtensionConfigSchema,
4
+ getAsyncLifecycle,
5
+ getSyncLifecycle,
6
+ registerBreadcrumbs,
7
+ registerFeatureFlag,
8
+ } from '@openmrs/esm-framework';
2
9
  import { configSchema } from './config-schema';
10
+ import { admissionRequestNoteRowConfigSchema } from './config-schema-admission-request-note';
11
+ import { coloredObsTagsCardRowConfigSchema } from './config-schema-extension-colored-obs-tags';
12
+ import { moduleName } from './constant';
13
+ import { createDashboardLink } from './createDashboardLink.component';
3
14
  import rootComponent from './root.component';
4
- import admissionRequestsWorkspace from "./ward-workspace/admission-requests-workspace.component"
5
15
 
6
16
  export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
7
17
 
8
- const moduleName = '@kenyaemr/esm-ward-app';
9
18
 
10
19
  const options = {
11
20
  featureName: 'ward',
@@ -14,10 +23,68 @@ const options = {
14
23
 
15
24
  export const root = getSyncLifecycle(rootComponent, options);
16
25
 
17
- export const admissionRequestWorkspace = getSyncLifecycle(admissionRequestsWorkspace, options);
26
+ export const wardDashboardLink = getSyncLifecycle(createDashboardLink({ name: 'ward', title: 'wards' }), options);
27
+
28
+ // t('admissionRequests', 'Admission Requests')
29
+ export const admissionRequestWorkspace = getAsyncLifecycle(
30
+ () => import('./ward-workspace/admission-request-workspace/admission-requests.workspace'),
31
+ options,
32
+ );
33
+
34
+ export const admitPatientFormWorkspace = getAsyncLifecycle(
35
+ () => import('./ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace'),
36
+ options,
37
+ );
38
+
39
+ // Title for this workspace is set dynamically
40
+ export const wardPatientWorkspace = getAsyncLifecycle(
41
+ () => import('./ward-patient-workspace/ward-patient.workspace'),
42
+ options,
43
+ );
44
+
45
+ // t("inpatientNotesWorkspaceTitle", "In-patient notes")
46
+ export const wardPatientNotesWorkspace = getAsyncLifecycle(
47
+ () => import('./ward-workspace/ward-patient-notes/notes.workspace'),
48
+ options,
49
+ );
50
+
51
+ export const wardPatientActionButtonExtension = getAsyncLifecycle(
52
+ () => import('./ward-patient-workspace/ward-patient-action-button.extension'),
53
+ options,
54
+ );
55
+
56
+ export const wardPatientNotesActionButtonExtension = getAsyncLifecycle(
57
+ () => import('./ward-workspace/ward-patient-notes/notes-action-button.extension'),
58
+ options,
59
+ );
60
+
61
+ export const coloredObsTagCardRowExtension = getAsyncLifecycle(
62
+ () => import('./ward-patient-card/card-rows/colored-obs-tags-card-row.extension'),
63
+ options,
64
+ );
65
+
66
+ export const admissionRequestNoteRowExtension = getAsyncLifecycle(
67
+ () => import('./ward-patient-card/card-rows/admission-request-note.extension'),
68
+ options,
69
+ );
70
+
71
+ // t('transfers', 'Transfers')
72
+ export const patientTransferAndSwapWorkspace = getAsyncLifecycle(
73
+ () => import('./ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace'),
74
+ options,
75
+ );
76
+
77
+ export const patientTransferAndSwapWorkspaceSiderailIcon = getAsyncLifecycle(
78
+ () => import('./action-menu-buttons/transfer-workspace-siderail.component'),
79
+ options,
80
+ );
81
+
18
82
  export function startupApp() {
19
83
  registerBreadcrumbs([]);
20
84
  defineConfigSchema(moduleName, configSchema);
85
+ defineExtensionConfigSchema('colored-obs-tags-card-row', coloredObsTagsCardRowConfigSchema);
86
+ defineExtensionConfigSchema('admission-request-note-card-row', admissionRequestNoteRowConfigSchema);
87
+
21
88
  registerFeatureFlag(
22
89
  'bedmanagement-module',
23
90
  'Bed Management Module',
@@ -0,0 +1,118 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { IconButton, RadioButton, RadioButtonGroup, RadioButtonSkeleton, Search } from '@carbon/react';
5
+ import { type RadioButtonGroupProps } from '@carbon/react/lib/components/RadioButtonGroup/RadioButtonGroup';
6
+ import {
7
+ ChevronLeftIcon,
8
+ ChevronRightIcon,
9
+ isDesktop,
10
+ ResponsiveWrapper,
11
+ useDebounce,
12
+ useLayoutType,
13
+ } from '@openmrs/esm-framework';
14
+ import useEmrConfiguration from '../hooks/useEmrConfiguration';
15
+ import useLocations from '../hooks/useLocations';
16
+ import styles from './location-selector.scss';
17
+
18
+ interface LocationSelectorProps extends RadioButtonGroupProps {}
19
+
20
+ export default function LocationSelector(props: LocationSelectorProps) {
21
+ const size = 5;
22
+ const { t } = useTranslation();
23
+ const { emrConfiguration, isLoadingEmrConfiguration } = useEmrConfiguration();
24
+ const isTablet = !isDesktop(useLayoutType());
25
+ const [searchTerm, setSearchTerm] = useState('');
26
+ const debouncedSearchTerm = useDebounce(searchTerm);
27
+ const [page, setPage] = useState(1);
28
+ const filterCriteria: Array<Array<string>> = useMemo(() => {
29
+ const criteria = [];
30
+ if (debouncedSearchTerm) {
31
+ criteria.push(['name:contains', debouncedSearchTerm]);
32
+ }
33
+ criteria.push(['_count', size.toString()]);
34
+ if (emrConfiguration) {
35
+ criteria.push(['_tag', emrConfiguration.supportsTransferLocationTag.name]);
36
+ }
37
+ if (page > 1) {
38
+ criteria.push(['_getpagesoffset', ((page - 1) * size).toString()]);
39
+ }
40
+ return criteria;
41
+ }, [debouncedSearchTerm, page, emrConfiguration]);
42
+ const { locations, isLoading, totalLocations } = useLocations(filterCriteria, !emrConfiguration);
43
+
44
+ const handlePageChange = useCallback(
45
+ ({ page: newPage }) => {
46
+ setPage(newPage);
47
+ },
48
+ [setPage, page],
49
+ );
50
+ const handleSearch = useCallback(
51
+ (event: React.ChangeEvent<HTMLInputElement>) => {
52
+ setSearchTerm(event.target.value);
53
+ },
54
+ [setSearchTerm],
55
+ );
56
+ return (
57
+ <div className={styles.locationSelector}>
58
+ <ResponsiveWrapper>
59
+ <Search
60
+ onChange={handleSearch}
61
+ value={searchTerm}
62
+ placeholder={t('searchLocations', 'Search locations')}
63
+ size={isTablet ? 'lg' : 'md'}
64
+ />
65
+ </ResponsiveWrapper>
66
+ {isLoading || isLoadingEmrConfiguration ? (
67
+ <div className={styles.radioButtonGroup}>
68
+ <RadioButtonSkeleton />
69
+ <RadioButtonSkeleton />
70
+ <RadioButtonSkeleton />
71
+ <RadioButtonSkeleton />
72
+ <RadioButtonSkeleton />
73
+ </div>
74
+ ) : (
75
+ <ResponsiveWrapper>
76
+ <RadioButtonGroup {...props} className={styles.radioButtonGroup} orientation="vertical">
77
+ {locations?.length > 0 ? (
78
+ locations?.map((location) => (
79
+ <RadioButton key={location.id} labelText={location.name} value={location.id} />
80
+ ))
81
+ ) : (
82
+ <span className={styles.bodyShort01}>{t('noLocationsFound', 'No locations found')}</span>
83
+ )}
84
+ </RadioButtonGroup>
85
+ </ResponsiveWrapper>
86
+ )}
87
+ {totalLocations > 5 && (
88
+ <div className={styles.pagination}>
89
+ <span className={styles.bodyShort01}>
90
+ {t('showingLocations', '{{start}}-{{end}} of {{count}} locations', {
91
+ start: (page - 1) * size + 1,
92
+ end: Math.min(page * size, totalLocations),
93
+ count: totalLocations,
94
+ })}
95
+ </span>
96
+ <div>
97
+ <IconButton
98
+ className={classNames(styles.button, styles.buttonLeft)}
99
+ disabled={page === 1}
100
+ kind="ghost"
101
+ label={t('previousPage', 'Previous page')}
102
+ onClick={() => handlePageChange({ page: page - 1 })}>
103
+ <ChevronLeftIcon />
104
+ </IconButton>
105
+ <IconButton
106
+ className={styles.button}
107
+ disabled={page * size >= totalLocations}
108
+ kind="ghost"
109
+ label={t('nextPage', 'Next page')}
110
+ onClick={() => handlePageChange({ page: page + 1 })}>
111
+ <ChevronRightIcon />
112
+ </IconButton>
113
+ </div>
114
+ </div>
115
+ )}
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,48 @@
1
+ @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .locationSelector {
5
+ border: 1px solid $ui-03;
6
+ background-color: $ui-01;
7
+ }
8
+
9
+ :global(.omrs-breakpoint-lt-desktop) .locationSelector {
10
+ background-color: $ui-02;
11
+ }
12
+
13
+ .radioButtonGroup {
14
+ padding: layout.$spacing-03 layout.$spacing-05;
15
+ width: 100%;
16
+
17
+ :global(.cds--radio-button-wrapper) {
18
+ padding: layout.$spacing-02;
19
+
20
+ :global(.cds--radio-button__label) {
21
+ align-items: center;
22
+ }
23
+ }
24
+ }
25
+
26
+ .pagination {
27
+ display: flex;
28
+ padding-left: layout.$spacing-05;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ border-top: 1px solid $ui-03;
32
+ }
33
+
34
+ .button {
35
+ svg {
36
+ fill: $ui-05 !important;
37
+ }
38
+ }
39
+
40
+ .buttonLeft {
41
+ border-right: 1px solid $ui-03 !important;
42
+ border-left: 1px solid $ui-03 !important;
43
+ }
44
+
45
+ .borderedButton {
46
+ border-left: 1px solid $ui-03;
47
+ border-right: 1px solid $ui-03;
48
+ }
@@ -3,7 +3,8 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
3
3
  import WardView from './ward-view/ward-view.component';
4
4
 
5
5
  const Root: React.FC = () => {
6
- const wardViewBasename = window.getOpenmrsSpaBase() + 'ward';
6
+ // t('wards', 'Wards')
7
+ const wardViewBasename = window.getOpenmrsSpaBase() + 'home/ward';
7
8
 
8
9
  return (
9
10
  <main>