@kenyaemr/esm-ward-app 8.0.1-pre.99 → 8.0.3-pre.131

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/.turbo/turbo-build.log +20 -24
  2. package/dist/109.js +1 -0
  3. package/dist/109.js.map +1 -0
  4. package/dist/124.js +1 -0
  5. package/dist/124.js.map +1 -0
  6. package/dist/125.js +1 -0
  7. package/dist/125.js.map +1 -0
  8. package/dist/130.js +1 -1
  9. package/dist/130.js.LICENSE.txt +2 -0
  10. package/dist/130.js.map +1 -1
  11. package/dist/146.js +1 -0
  12. package/dist/146.js.map +1 -0
  13. package/dist/15.js +1 -0
  14. package/dist/15.js.map +1 -0
  15. package/dist/153.js +1 -0
  16. package/dist/153.js.map +1 -0
  17. package/dist/303.js +2 -1
  18. package/dist/303.js.map +1 -1
  19. package/dist/325.js +1 -0
  20. package/dist/325.js.map +1 -0
  21. package/dist/372.js +2 -0
  22. package/dist/372.js.map +1 -0
  23. package/dist/471.js +1 -0
  24. package/dist/471.js.map +1 -0
  25. package/dist/481.js +1 -0
  26. package/dist/481.js.map +1 -0
  27. package/dist/53.js +1 -0
  28. package/dist/53.js.map +1 -0
  29. package/dist/{960.js → 559.js} +1 -1
  30. package/dist/559.js.map +1 -0
  31. package/dist/574.js +1 -1
  32. package/dist/576.js +1 -0
  33. package/dist/576.js.map +1 -0
  34. package/dist/577.js +1 -1
  35. package/dist/577.js.map +1 -1
  36. package/dist/{255.js → 649.js} +2 -2
  37. package/dist/649.js.LICENSE.txt +9 -0
  38. package/dist/649.js.map +1 -0
  39. package/dist/{659.js → 662.js} +1 -1
  40. package/dist/662.js.map +1 -0
  41. package/dist/920.js +1 -0
  42. package/dist/920.js.map +1 -0
  43. package/dist/921.js +1 -0
  44. package/dist/921.js.map +1 -0
  45. package/dist/922.js +1 -0
  46. package/dist/922.js.map +1 -0
  47. package/dist/969.js +1 -0
  48. package/dist/969.js.map +1 -0
  49. package/dist/kenyaemr-esm-ward-app.js +1 -1
  50. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +304 -128
  51. package/dist/kenyaemr-esm-ward-app.js.map +1 -1
  52. package/dist/main.js +1 -1
  53. package/dist/main.js.LICENSE.txt +0 -10
  54. package/dist/main.js.map +1 -1
  55. package/dist/routes.json +1 -1
  56. package/mock.tsx +62 -0
  57. package/package-lock.json +5001 -0
  58. package/package.json +2 -2
  59. package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +37 -0
  60. package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +20 -0
  61. package/src/beds/empty-bed-skeleton.tsx +4 -3
  62. package/src/beds/empty-bed.component.tsx +3 -3
  63. package/src/beds/ward-bed.component.tsx +41 -0
  64. package/src/beds/ward-bed.scss +45 -0
  65. package/src/beds/{occupied-bed.test.tsx → ward-bed.test.tsx} +42 -20
  66. package/src/config-schema.ts +203 -84
  67. package/src/constant.ts +1 -1
  68. package/src/hooks/useAdmissionLocation.ts +22 -4
  69. package/src/hooks/useAssignedBedByPatient.ts +9 -0
  70. package/src/hooks/useBeds.ts +3 -4
  71. package/src/hooks/useConcept.ts +3 -4
  72. package/src/hooks/useEmrConfiguration.ts +5 -0
  73. package/src/hooks/useInpatientAdmission.ts +9 -14
  74. package/src/hooks/useInpatientRequest.ts +4 -15
  75. package/src/hooks/useLocations.ts +8 -51
  76. package/src/hooks/useMotherAndChildren.ts +46 -0
  77. package/src/hooks/useObs.ts +3 -7
  78. package/src/hooks/usePatientPendingOrders.ts +16 -0
  79. package/src/hooks/useWardPatientGrouping.ts +32 -0
  80. package/src/index.ts +45 -17
  81. package/src/location-selector/location-selector.component.tsx +18 -21
  82. package/src/root.component.tsx +3 -0
  83. package/src/routes.json +41 -3
  84. package/src/types/index.ts +50 -1
  85. package/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx +38 -0
  86. package/src/ward-patient-card/card-rows/coded-obs-tags-row.component.tsx +108 -0
  87. package/src/ward-patient-card/card-rows/mother-child-row.component.tsx +84 -0
  88. package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
  89. package/src/ward-patient-card/card-rows/pending-items-row.component.tsx +54 -0
  90. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +1 -1
  91. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +62 -39
  92. package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +5 -5
  93. package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +18 -41
  94. package/src/ward-patient-card/row-elements/ward-patient-location.tsx +19 -0
  95. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +38 -34
  96. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +26 -13
  97. package/src/ward-patient-card/row-elements/ward-patient-pending-order.component.tsx +45 -0
  98. package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.tsx +38 -0
  99. package/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx +32 -0
  100. package/src/ward-patient-card/row-elements/ward-patient-skeleton-text.tsx +9 -0
  101. package/src/ward-patient-card/ward-patient-card.component.tsx +14 -45
  102. package/src/ward-patient-card/ward-patient-card.scss +68 -9
  103. package/src/ward-view/default-ward/default-ward-beds.component.tsx +42 -0
  104. package/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx +32 -0
  105. package/src/ward-view/default-ward/default-ward-patient-card.component.tsx +31 -0
  106. package/src/ward-view/default-ward/default-ward-pending-patients.component.tsx +52 -0
  107. package/src/ward-view/default-ward/default-ward-unassigned-patients.component.tsx +32 -0
  108. package/src/ward-view/default-ward/default-ward-view.component.tsx +31 -0
  109. package/src/ward-view/materal-ward/maternal-ward-beds.component.tsx +65 -0
  110. package/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx +30 -0
  111. package/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx +93 -0
  112. package/src/{beds/occupied-bed.scss → ward-view/materal-ward/maternal-ward-patient-card.scss} +4 -9
  113. package/src/ward-view/materal-ward/maternal-ward-patient-card.test.tsx +58 -0
  114. package/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx +48 -0
  115. package/src/ward-view/materal-ward/maternal-ward-unassigned-patients.component.tsx +33 -0
  116. package/src/ward-view/materal-ward/maternal-ward-view.component.tsx +38 -0
  117. package/src/ward-view/materal-ward/maternal-ward-view.resource.ts +89 -0
  118. package/src/ward-view/ward-view.component.tsx +15 -163
  119. package/src/ward-view/ward-view.resource.ts +193 -1
  120. package/src/ward-view/ward-view.scss +17 -6
  121. package/src/ward-view/ward-view.test.tsx +43 -48
  122. package/src/ward-view/ward.component.tsx +106 -0
  123. package/src/ward-view-header/admission-requests-bar.component.tsx +14 -9
  124. package/src/ward-view-header/admission-requests-bar.test.tsx +11 -23
  125. package/src/ward-view-header/admission-requests.scss +1 -1
  126. package/src/ward-view-header/ward-metric.component.tsx +24 -0
  127. package/src/ward-view-header/ward-metric.scss +25 -0
  128. package/src/ward-view-header/ward-metrics.component.tsx +78 -0
  129. package/src/ward-view-header/ward-metrics.scss +7 -0
  130. package/src/ward-view-header/ward-metrics.test.tsx +37 -0
  131. package/src/ward-view-header/ward-view-header.component.tsx +9 -4
  132. package/src/ward-view-header/ward-view-header.scss +0 -1
  133. package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +70 -6
  134. package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +10 -23
  135. package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +9 -3
  136. package/src/ward-workspace/admission-request-card/admission-request-card.scss +13 -4
  137. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +55 -33
  138. package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +30 -37
  139. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +98 -203
  140. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +116 -180
  141. package/src/ward-workspace/bed-selector.component.tsx +119 -0
  142. package/src/ward-workspace/bed-selector.scss +15 -0
  143. package/src/ward-workspace/patient-banner/patient-banner.component.tsx +7 -14
  144. package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +23 -0
  145. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient-action-button.extension.tsx +2 -1
  146. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.workspace.tsx +18 -9
  147. package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
  148. package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +113 -0
  149. package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +68 -79
  150. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +24 -24
  151. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +12 -2
  152. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +12 -8
  153. package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +11 -0
  154. package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +1 -1
  155. package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +1 -1
  156. package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +2 -2
  157. package/src/ward-workspace/ward-patient-notes/notes.resource.ts +5 -7
  158. package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +1 -1
  159. package/src/ward-workspace/ward-patient-notes/types.ts +0 -4
  160. package/src/ward.resource.ts +38 -2
  161. package/translations/en.json +31 -7
  162. package/dist/152.js +0 -1
  163. package/dist/152.js.map +0 -1
  164. package/dist/255.js.map +0 -1
  165. package/dist/269.js +0 -1
  166. package/dist/269.js.map +0 -1
  167. package/dist/346.js +0 -1
  168. package/dist/346.js.map +0 -1
  169. package/dist/466.js +0 -1
  170. package/dist/466.js.map +0 -1
  171. package/dist/659.js.map +0 -1
  172. package/dist/729.js +0 -1
  173. package/dist/729.js.map +0 -1
  174. package/dist/749.js +0 -1
  175. package/dist/749.js.map +0 -1
  176. package/dist/76.js +0 -1
  177. package/dist/76.js.map +0 -1
  178. package/dist/793.js +0 -2
  179. package/dist/793.js.map +0 -1
  180. package/dist/803.js +0 -1
  181. package/dist/803.js.map +0 -1
  182. package/dist/960.js.map +0 -1
  183. package/src/beds/empty-bed.scss +0 -28
  184. package/src/beds/occupied-bed.component.tsx +0 -35
  185. package/src/beds/unassigned-patient.component.tsx +0 -20
  186. package/src/beds/unassigned-patient.scss +0 -6
  187. package/src/config-schema-admission-request-note.ts +0 -9
  188. package/src/config-schema-extension-colored-obs-tags.ts +0 -91
  189. package/src/hooks/useCurrentWardCardConfig.ts +0 -32
  190. package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +0 -27
  191. package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +0 -13
  192. package/src/ward-patient-card/ward-patient-card-element.component.tsx +0 -65
  193. package/src/ward-view/ward-bed.component.tsx +0 -14
  194. /package/dist/{793.js.LICENSE.txt → 303.js.LICENSE.txt} +0 -0
  195. /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
  196. /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
@@ -1,21 +1,17 @@
1
- import useSWR from 'swr';
1
+ import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
2
2
  import { type Observation } from '../types';
3
- import { type Link, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
4
3
 
5
4
  interface ObsSearchCriteria {
6
5
  patient: string;
7
6
  concept: string;
8
7
  }
9
8
 
10
- export function useObs(criteria?: ObsSearchCriteria, representation = 'default') {
9
+ export function useObs(criteria?: ObsSearchCriteria, fetch: boolean = true, representation = 'default') {
11
10
  const params = new URLSearchParams({
12
11
  ...criteria,
13
12
  v: representation,
14
13
  });
15
14
 
16
15
  const apiUrl = `${restBaseUrl}/obs?${params}`;
17
- return useSWR<{ data: { results: Array<Observation>; totalCount: number; links: Array<Link> } }, Error>(
18
- apiUrl,
19
- openmrsFetch,
20
- );
16
+ return useOpenmrsFetchAll<Observation>(fetch ? apiUrl : null);
21
17
  }
@@ -0,0 +1,16 @@
1
+ import { type FetchResponse, openmrsFetch, type OpenmrsResource, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+
4
+ export function usePatientPendingOrders(patientUuid: string, orderTypeUUid: string, visitStartDate: string) {
5
+ const apiUrl =
6
+ patientUuid && orderTypeUUid && visitStartDate
7
+ ? `${restBaseUrl}/order?includeNullFulfillerStatus=true&patient=${patientUuid}&orderTypes=${orderTypeUUid}&activatedOnOrAfterDate=${visitStartDate}`
8
+ : null;
9
+ const { data, ...rest } = useSWR<FetchResponse<{ results: Array<OpenmrsResource> }>, Error>(apiUrl, openmrsFetch);
10
+
11
+ return {
12
+ orders: data?.data.results,
13
+ count: data?.data.results.length,
14
+ ...rest,
15
+ };
16
+ }
@@ -0,0 +1,32 @@
1
+ import { useMemo } from 'react';
2
+ import { createAndGetWardPatientGrouping } from '../ward-view/ward-view.resource';
3
+ import { useAdmissionLocation } from './useAdmissionLocation';
4
+ import { useInpatientAdmission } from './useInpatientAdmission';
5
+ import { useInpatientRequest } from './useInpatientRequest';
6
+
7
+ export function useWardPatientGrouping() {
8
+ const admissionLocationResponse = useAdmissionLocation();
9
+ const inpatientAdmissionResponse = useInpatientAdmission();
10
+ const inpatientRequestResponse = useInpatientRequest();
11
+
12
+ const { data: inpatientAdmissions } = inpatientAdmissionResponse;
13
+ const { admissionLocation } = admissionLocationResponse;
14
+ const { inpatientRequests } = inpatientRequestResponse;
15
+
16
+ const groupings = useMemo(() => {
17
+ return { ...createAndGetWardPatientGrouping(inpatientAdmissions, admissionLocation, inpatientRequests) };
18
+ }, [admissionLocation, inpatientAdmissions, inpatientRequests]) as ReturnType<typeof createAndGetWardPatientGrouping>;
19
+ return {
20
+ ...groupings,
21
+ admissionLocationResponse,
22
+ inpatientAdmissionResponse,
23
+ inpatientRequestResponse,
24
+ isLoading:
25
+ admissionLocationResponse.isLoading || inpatientAdmissionResponse.isLoading || inpatientRequestResponse.isLoading,
26
+ mutate() {
27
+ admissionLocationResponse?.mutate();
28
+ inpatientAdmissionResponse?.mutate();
29
+ inpatientRequestResponse?.mutate();
30
+ },
31
+ };
32
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,11 @@
1
1
  import {
2
2
  defineConfigSchema,
3
- defineExtensionConfigSchema,
4
3
  getAsyncLifecycle,
5
4
  getSyncLifecycle,
6
5
  registerBreadcrumbs,
7
6
  registerFeatureFlag,
8
7
  } from '@openmrs/esm-framework';
9
8
  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
9
  import { moduleName } from './constant';
13
10
  import { createDashboardLink } from './createDashboardLink.component';
14
11
  import rootComponent from './root.component';
@@ -38,7 +35,7 @@ export const admitPatientFormWorkspace = getAsyncLifecycle(
38
35
 
39
36
  // Title for this workspace is set dynamically
40
37
  export const wardPatientWorkspace = getAsyncLifecycle(
41
- () => import('./ward-patient-workspace/ward-patient.workspace'),
38
+ () => import('./ward-workspace/patient-details/ward-patient.workspace'),
42
39
  options,
43
40
  );
44
41
 
@@ -49,7 +46,7 @@ export const wardPatientNotesWorkspace = getAsyncLifecycle(
49
46
  );
50
47
 
51
48
  export const wardPatientActionButtonExtension = getAsyncLifecycle(
52
- () => import('./ward-patient-workspace/ward-patient-action-button.extension'),
49
+ () => import('./ward-workspace/patient-details/ward-patient-action-button.extension'),
53
50
  options,
54
51
  );
55
52
 
@@ -58,36 +55,67 @@ export const wardPatientNotesActionButtonExtension = getAsyncLifecycle(
58
55
  options,
59
56
  );
60
57
 
61
- export const coloredObsTagCardRowExtension = getAsyncLifecycle(
62
- () => import('./ward-patient-card/card-rows/colored-obs-tags-card-row.extension'),
58
+ // t('transfers', 'Transfers')
59
+ export const patientTransferAndSwapWorkspace = getAsyncLifecycle(
60
+ () => import('./ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace'),
63
61
  options,
64
62
  );
65
63
 
66
- export const admissionRequestNoteRowExtension = getAsyncLifecycle(
67
- () => import('./ward-patient-card/card-rows/admission-request-note.extension'),
64
+ // t('discharge', 'Discharge')
65
+ export const patientDischargeWorkspace = getAsyncLifecycle(
66
+ () => import('./ward-workspace/patient-discharge/patient-discharge.workspace'),
68
67
  options,
69
68
  );
70
69
 
71
- // t('transfers', 'Transfers')
72
- export const patientTransferAndSwapWorkspace = getAsyncLifecycle(
73
- () => import('./ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace'),
70
+ export const patientTransferAndSwapWorkspaceSiderailIcon = getAsyncLifecycle(
71
+ () => import('./action-menu-buttons/transfer-workspace-siderail.component'),
74
72
  options,
75
73
  );
76
74
 
77
- export const patientTransferAndSwapWorkspaceSiderailIcon = getAsyncLifecycle(
78
- () => import('./action-menu-buttons/transfer-workspace-siderail.component'),
75
+ // t('transferRequest', 'Transfer request')
76
+ export const patientTransferRequestWorkspace = getAsyncLifecycle(
77
+ () => import('./ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace'),
78
+ options,
79
+ );
80
+
81
+ export const patientDischargeWorkspaceSideRailIcon = getAsyncLifecycle(
82
+ () => import('./action-menu-buttons/discharge-workspace-siderail.component'),
83
+ options,
84
+ );
85
+
86
+ export const patientClinicalFormsWorkspace = getAsyncLifecycle(
87
+ () => import('./ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace'),
88
+ options,
89
+ );
90
+
91
+ export const clinicalFormWorkspaceSideRailIcon = getAsyncLifecycle(
92
+ () => import('./action-menu-buttons/clinical-forms-workspace-siderail.component'),
93
+ options,
94
+ );
95
+
96
+ export const defaultWardView = getAsyncLifecycle(
97
+ () => import('./ward-view/default-ward/default-ward-view.component'),
98
+ options,
99
+ );
100
+
101
+ export const maternalWardView = getAsyncLifecycle(
102
+ () => import('./ward-view/materal-ward/maternal-ward-view.component'),
79
103
  options,
80
104
  );
81
105
 
82
106
  export function startupApp() {
83
107
  registerBreadcrumbs([]);
84
108
  defineConfigSchema(moduleName, configSchema);
85
- defineExtensionConfigSchema('colored-obs-tags-card-row', coloredObsTagsCardRowConfigSchema);
86
- defineExtensionConfigSchema('admission-request-note-card-row', admissionRequestNoteRowConfigSchema);
87
109
 
88
110
  registerFeatureFlag(
89
111
  'bedmanagement-module',
90
- 'Bed Management Module',
112
+ 'Bed management module',
91
113
  'Enables features related to bed management / assignment. Requires the backend bed management module to be installed.',
92
114
  );
115
+
116
+ registerFeatureFlag(
117
+ 'ward-view-vertical-tiling',
118
+ 'Ward view vertical tiling',
119
+ 'Enable tiling of bed cards vertically in the ward view.',
120
+ );
93
121
  }
@@ -24,29 +24,26 @@ export default function LocationSelector(props: LocationSelectorProps) {
24
24
  const isTablet = !isDesktop(useLayoutType());
25
25
  const [searchTerm, setSearchTerm] = useState('');
26
26
  const debouncedSearchTerm = useDebounce(searchTerm);
27
- const [page, setPage] = useState(1);
28
27
  const filterCriteria: Array<Array<string>> = useMemo(() => {
29
28
  const criteria = [];
30
29
  if (debouncedSearchTerm) {
31
30
  criteria.push(['name:contains', debouncedSearchTerm]);
32
31
  }
33
- criteria.push(['_count', size.toString()]);
34
32
  if (emrConfiguration) {
35
33
  criteria.push(['_tag', emrConfiguration.supportsTransferLocationTag.name]);
36
34
  }
37
- if (page > 1) {
38
- criteria.push(['_getpagesoffset', ((page - 1) * size).toString()]);
39
- }
40
35
  return criteria;
41
- }, [debouncedSearchTerm, page, emrConfiguration]);
42
- const { locations, isLoading, totalLocations } = useLocations(filterCriteria, !emrConfiguration);
36
+ }, [debouncedSearchTerm, emrConfiguration]);
37
+ const {
38
+ data: locations,
39
+ isLoading,
40
+ totalCount,
41
+ currentPage,
42
+ totalPages,
43
+ goToNext,
44
+ goToPrevious,
45
+ } = useLocations(filterCriteria, size, !emrConfiguration);
43
46
 
44
- const handlePageChange = useCallback(
45
- ({ page: newPage }) => {
46
- setPage(newPage);
47
- },
48
- [setPage, page],
49
- );
50
47
  const handleSearch = useCallback(
51
48
  (event: React.ChangeEvent<HTMLInputElement>) => {
52
49
  setSearchTerm(event.target.value);
@@ -84,30 +81,30 @@ export default function LocationSelector(props: LocationSelectorProps) {
84
81
  </RadioButtonGroup>
85
82
  </ResponsiveWrapper>
86
83
  )}
87
- {totalLocations > 5 && (
84
+ {totalCount > size && (
88
85
  <div className={styles.pagination}>
89
86
  <span className={styles.bodyShort01}>
90
87
  {t('showingLocations', '{{start}}-{{end}} of {{count}} locations', {
91
- start: (page - 1) * size + 1,
92
- end: Math.min(page * size, totalLocations),
93
- count: totalLocations,
88
+ start: (currentPage - 1) * size + 1,
89
+ end: Math.min(currentPage * size, totalCount),
90
+ count: totalCount,
94
91
  })}
95
92
  </span>
96
93
  <div>
97
94
  <IconButton
98
95
  className={classNames(styles.button, styles.buttonLeft)}
99
- disabled={page === 1}
96
+ disabled={currentPage === 1}
100
97
  kind="ghost"
101
98
  label={t('previousPage', 'Previous page')}
102
- onClick={() => handlePageChange({ page: page - 1 })}>
99
+ onClick={() => goToPrevious()}>
103
100
  <ChevronLeftIcon />
104
101
  </IconButton>
105
102
  <IconButton
106
103
  className={styles.button}
107
- disabled={page * size >= totalLocations}
104
+ disabled={currentPage >= totalPages}
108
105
  kind="ghost"
109
106
  label={t('nextPage', 'Next page')}
110
- onClick={() => handlePageChange({ page: page + 1 })}>
107
+ onClick={() => goToNext()}>
111
108
  <ChevronRightIcon />
112
109
  </IconButton>
113
110
  </div>
@@ -1,3 +1,4 @@
1
+ import { WorkspaceContainer } from '@openmrs/esm-framework';
1
2
  import React from 'react';
2
3
  import { BrowserRouter, Route, Routes } from 'react-router-dom';
3
4
  import WardView from './ward-view/ward-view.component';
@@ -14,6 +15,8 @@ const Root: React.FC = () => {
14
15
  <Route path="/:locationUuid" element={<WardView />} />
15
16
  </Routes>
16
17
  </BrowserRouter>
18
+
19
+ <WorkspaceContainer overlay contextKey="ward" />
17
20
  </main>
18
21
  );
19
22
  };
package/src/routes.json CHANGED
@@ -51,9 +51,24 @@
51
51
  "component": "patientTransferAndSwapWorkspaceSiderailIcon"
52
52
  },
53
53
  {
54
- "component": "admissionRequestNoteRowExtension",
55
- "name": "admission-request-note-card-row",
56
- "slot": "ward-patient-card-slot"
54
+ "name": "patient-discharge-siderail-button",
55
+ "slot": "action-menu-ward-patient-items-slot",
56
+ "component": "patientDischargeWorkspaceSideRailIcon"
57
+ },
58
+ {
59
+ "name": "clinical-forms-workspace-siderail-button",
60
+ "component": "clinicalFormWorkspaceSideRailIcon",
61
+ "slot": "action-menu-ward-patient-items-slot"
62
+ },
63
+ {
64
+ "component": "defaultWardView",
65
+ "name": "default-ward",
66
+ "slot": "default-ward"
67
+ },
68
+ {
69
+ "component": "maternalWardView",
70
+ "name": "maternal-ward",
71
+ "slot": "maternal-ward"
57
72
  }
58
73
  ],
59
74
  "workspaces": [
@@ -93,6 +108,29 @@
93
108
  "type": "transfer-swap-bed-form",
94
109
  "hasOwnSidebar": true,
95
110
  "sidebarFamily": "ward-patient"
111
+ },
112
+ {
113
+ "name": "patient-transfer-request-workspace",
114
+ "component": "patientTransferRequestWorkspace",
115
+ "title": "transferRequest",
116
+ "type": "transfer-request-form"
117
+ },
118
+ {
119
+ "name": "patient-discharge-workspace",
120
+ "component": "patientDischargeWorkspace",
121
+ "title": "discharge",
122
+ "type": "ward-patient-discharge",
123
+ "hasOwnSidebar": true,
124
+ "sidebarFamily": "ward-patient"
125
+ },
126
+ {
127
+ "name": "ward-patient-clinical-forms-workspace",
128
+ "component": "patientClinicalFormsWorkspace",
129
+ "title": "clinicalForms",
130
+ "type": "ward-patient-clinical-forms",
131
+ "hasOwnSidebar": true,
132
+ "sidebarFamily": "ward-patient",
133
+ "width": "wider"
96
134
  }
97
135
  ]
98
136
  }
@@ -9,8 +9,9 @@ import type {
9
9
  Visit,
10
10
  } from '@openmrs/esm-framework';
11
11
  import type React from 'react';
12
+ import type { useWardPatientGrouping } from '../hooks/useWardPatientGrouping';
12
13
 
13
- export type WardPatientCard = React.FC<WardPatient>;
14
+ export type WardPatientCardType = React.FC<WardPatient>;
14
15
 
15
16
  // WardPatient is a patient admitted to a ward, and/or in a bed on a ward
16
17
  export type WardPatient = {
@@ -40,8 +41,10 @@ export type WardPatient = {
40
41
  */
41
42
  inpatientRequest: InpatientRequest;
42
43
  };
44
+
43
45
  export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps {
44
46
  wardPatient: WardPatient;
47
+ WardPatientHeader: React.FC<WardPatient>;
45
48
  }
46
49
 
47
50
  // server-side types defined in openmrs-module-bedmanagement:
@@ -64,6 +67,14 @@ export interface Bed {
64
67
  status: BedStatus;
65
68
  }
66
69
 
70
+ export interface BedDetail {
71
+ bedId: number;
72
+ bedNumber: number;
73
+ bedType: BedType;
74
+ physicalLocation: Location;
75
+ patients: Array<Patient>;
76
+ }
77
+
67
78
  export interface BedLayout {
68
79
  rowNumber: number;
69
80
  columnNumber: number;
@@ -134,6 +145,16 @@ export interface InpatientAdmission {
134
145
  // the first encounter of the visit that is of encounterType "Admission" or "Transfer",
135
146
  // regardless of the admission location
136
147
  firstAdmissionOrTransferEncounter: Encounter;
148
+
149
+ // the current in patient request
150
+ currentInpatientRequest: InpatientRequest;
151
+ }
152
+
153
+ export interface MotherAndChild {
154
+ mother: Patient;
155
+ child: Patient;
156
+ motherAdmission: InpatientAdmission;
157
+ childAdmission: InpatientAdmission;
137
158
  }
138
159
 
139
160
  // TODO: Move these types to esm-core
@@ -185,6 +206,12 @@ export interface EncounterRole extends OpenmrsResourceStrict {
185
206
  retired?: boolean;
186
207
  }
187
208
 
209
+ export interface WardMetrics {
210
+ patients: string;
211
+ freeBeds: string;
212
+ capacity: string;
213
+ }
214
+
188
215
  export interface EncounterPayload {
189
216
  encounterDatetime?: string;
190
217
  encounterType: string;
@@ -202,3 +229,25 @@ export interface ObsPayload {
202
229
  value?: string;
203
230
  groupMembers?: Array<ObsPayload>;
204
231
  }
232
+
233
+ export type WardPatientGroupDetails = ReturnType<typeof useWardPatientGrouping>;
234
+ export interface WardViewContext {
235
+ wardPatientGroupDetails: WardPatientGroupDetails;
236
+ WardPatientHeader: React.FC<WardPatient>;
237
+ }
238
+
239
+ export interface PatientAndAdmission {
240
+ patient: Patient;
241
+ currentAdmission: InpatientAdmission;
242
+ }
243
+
244
+ export interface MotherChildRelationships {
245
+ motherByChildUuid: Map<string, PatientAndAdmission>;
246
+ childrenByMotherUuid: Map<string, PatientAndAdmission[]>;
247
+ }
248
+
249
+ export interface MaternalWardViewContext {
250
+ motherChildRelationships: MotherChildRelationships;
251
+ }
252
+
253
+ export type PatientWorkspaceAdditionalProps = Omit<WardPatientWorkspaceProps, keyof DefaultWorkspaceProps>;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { type ObsElementConfig } from '../../config-schema';
3
+ import { type WardPatient } from '../../types';
4
+ import { useElementConfig } from '../../ward-view/ward-view.resource';
5
+ import WardPatientObs from '../row-elements/ward-patient-obs';
6
+ import styles from '../ward-patient-card.scss';
7
+
8
+ interface AdmissionRequestNoteRowProps {
9
+ wardPatient: WardPatient;
10
+ id: string;
11
+ }
12
+
13
+ const AdmissionRequestNoteRow: React.FC<AdmissionRequestNoteRowProps> = ({ id, wardPatient }) => {
14
+ const { patient, visit, inpatientAdmission } = wardPatient;
15
+ const { conceptUuid } = useElementConfig('admissionRequestNote', id) ?? {};
16
+ const config: ObsElementConfig = {
17
+ conceptUuid,
18
+ limit: 0,
19
+ id: 'admission-note',
20
+ onlyWithinCurrentVisit: true,
21
+ orderBy: 'ascending',
22
+ label: 'Admission Note',
23
+ };
24
+
25
+ // only show if the patient has not been admitted yet
26
+ const admitted = inpatientAdmission != null;
27
+ if (admitted) {
28
+ return null;
29
+ } else {
30
+ return (
31
+ <div className={styles.wardPatientCardRow}>
32
+ <WardPatientObs id={id} configOverride={config} patient={patient} visit={visit} />
33
+ </div>
34
+ );
35
+ }
36
+ };
37
+
38
+ export default AdmissionRequestNoteRow;
@@ -0,0 +1,108 @@
1
+ import { Tag } from '@carbon/react';
2
+ import { type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework';
3
+ import React, { type ReactNode } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { useObs } from '../../hooks/useObs';
6
+ import { useElementConfig } from '../../ward-view/ward-view.resource';
7
+ import styles from '../ward-patient-card.scss';
8
+ import WardPatientSkeletonText from '../row-elements/ward-patient-skeleton-text';
9
+ import {
10
+ getObsEncounterString,
11
+ obsCustomRepresentation,
12
+ useConceptToTagColorMap,
13
+ } from '../row-elements/ward-patient-obs.resource';
14
+ import WardPatientResponsiveTooltip from '../row-elements/ward-patient-responsive-tooltip';
15
+
16
+ interface WardPatientCodedObsTagsRowProps {
17
+ id: string;
18
+ patient: Patient;
19
+ visit: Visit;
20
+ }
21
+
22
+ /**
23
+ * The WardPatientCodedObsTags displays observations of coded values of a particular concept in the active visit as tags.
24
+ * Typically, these are taken from checkbox fields from a form. Each answer value can either be configured
25
+ * to show as its own tag, or collapsed into a summary tag show the number of these values present.
26
+ *
27
+ * This is a rather specialized element;
28
+ * for a more general display of obs value, use WardPatientObs instead.
29
+ * @param config
30
+ * @returns
31
+ */
32
+ const CodedObsTagsRow: React.FC<WardPatientCodedObsTagsRowProps> = ({ id, patient, visit }) => {
33
+ const config = useElementConfig('coloredObsTags', id);
34
+ const { conceptUuid, summaryLabel, summaryLabelColor } = config ?? {};
35
+ const { data, isLoading } = useObs(
36
+ { patient: patient.uuid, concept: conceptUuid },
37
+ conceptUuid != null,
38
+ obsCustomRepresentation,
39
+ );
40
+ const { t } = useTranslation();
41
+ const conceptToTagColorMap = useConceptToTagColorMap(config?.tags ?? []);
42
+
43
+ if (isLoading) {
44
+ return (
45
+ <div className={styles.wardPatientCardRow}>
46
+ <WardPatientSkeletonText />
47
+ </div>
48
+ );
49
+ } else {
50
+ const obsToDisplay = data?.filter((o) => {
51
+ const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
52
+ return matchVisit;
53
+ });
54
+
55
+ const summaryLabelToDisplay = summaryLabel != null ? t(summaryLabel) : obsToDisplay?.[0]?.concept?.display;
56
+
57
+ // for each obs configured to be displayed with a color, we create a tag for it
58
+ // for other obs not configured, we create a single summary tag for all of them.
59
+ const summaryTagTooltipText: ReactNode[] = [];
60
+ const coloredOpsTags = obsToDisplay
61
+ ?.map((o) => {
62
+ const { display, uuid } = o.value as OpenmrsResource;
63
+
64
+ const color = conceptToTagColorMap?.get(uuid);
65
+ if (color) {
66
+ return (
67
+ <WardPatientResponsiveTooltip tooltipContent={getObsEncounterString(o, t)}>
68
+ <Tag type={color} key={`ward-coded-obs-tag-${o.uuid}`}>
69
+ {display}
70
+ </Tag>
71
+ </WardPatientResponsiveTooltip>
72
+ );
73
+ } else {
74
+ summaryTagTooltipText.push(
75
+ <div key={uuid}>
76
+ {display} ({getObsEncounterString(o, t)})
77
+ </div>,
78
+ );
79
+ return null;
80
+ }
81
+ })
82
+ .filter((o) => o != null);
83
+
84
+ if (coloredOpsTags?.length > 0 || summaryTagTooltipText.length > 0) {
85
+ return (
86
+ <div className={styles.wardPatientCardRow}>
87
+ <span className={styles.wardPatientObsLabel}>
88
+ {coloredOpsTags}
89
+ {summaryTagTooltipText.length > 0 ? (
90
+ <WardPatientResponsiveTooltip tooltipContent={summaryTagTooltipText}>
91
+ <Tag type={summaryLabelColor}>
92
+ {t('countItems', '{{count}} {{item}}', {
93
+ count: summaryTagTooltipText.length,
94
+ item: summaryLabelToDisplay,
95
+ })}
96
+ </Tag>
97
+ </WardPatientResponsiveTooltip>
98
+ ) : null}
99
+ </span>
100
+ </div>
101
+ );
102
+ } else {
103
+ return null;
104
+ }
105
+ }
106
+ };
107
+
108
+ export default CodedObsTagsRow;
@@ -0,0 +1,84 @@
1
+ import { BabyIcon, MotherIcon, type Patient, useAppContext } from '@openmrs/esm-framework';
2
+ import classNames from 'classnames';
3
+ import React from 'react';
4
+ import { type InpatientAdmission, type MaternalWardViewContext } from '../../types';
5
+ import { type MaternalWardPatientCardProps } from '../../ward-view/materal-ward/maternal-ward-patient-card.component';
6
+ import WardPatientAge from '../row-elements/ward-patient-age';
7
+ import WardPatientIdentifier from '../row-elements/ward-patient-identifier';
8
+ import WardPatientLocation from '../row-elements/ward-patient-location';
9
+ import WardPatientName from '../row-elements/ward-patient-name';
10
+ import wardPatientCardStyles from '../ward-patient-card.scss';
11
+ import styles from './mother-child-row.scss';
12
+
13
+ /**
14
+ * This component displays the mother or children of the patient in the patient card. The patient's child is
15
+ * not displayed if it is in the same bed as the patient
16
+ *
17
+ * @param param0
18
+ * @returns
19
+ */
20
+ const MotherChildRow: React.FC<MaternalWardPatientCardProps> = ({ wardPatient, childrenOfWardPatientInSameBed }) => {
21
+ const { patient } = wardPatient;
22
+
23
+ const { motherChildRelationships } = useAppContext<MaternalWardViewContext>('maternal-ward-view-context') ?? {};
24
+
25
+ const { childrenByMotherUuid, motherByChildUuid } = motherChildRelationships ?? {};
26
+
27
+ const motherOfPatient = motherByChildUuid?.get(patient.uuid);
28
+ const childrenOfPatient = childrenByMotherUuid?.get(patient.uuid);
29
+ const childrenOfPatientNotInSameBed = childrenOfPatient?.filter((child) => {
30
+ return !childrenOfWardPatientInSameBed?.some((childInSameBed) => childInSameBed.patient.uuid == child.patient.uuid);
31
+ });
32
+
33
+ return (
34
+ <>
35
+ {motherOfPatient && (
36
+ <MotherOrChild
37
+ otherPatient={motherOfPatient.patient}
38
+ otherPatientAdmission={motherOfPatient.currentAdmission}
39
+ isOtherPatientTheMother={true}
40
+ />
41
+ )}
42
+ {childrenOfPatientNotInSameBed?.map((childOfPatient) => (
43
+ <MotherOrChild
44
+ key={childOfPatient.patient.uuid}
45
+ otherPatient={childOfPatient.patient}
46
+ otherPatientAdmission={childOfPatient.currentAdmission}
47
+ isOtherPatientTheMother={false}
48
+ />
49
+ ))}
50
+ </>
51
+ );
52
+ };
53
+
54
+ interface MotherOrChildProp {
55
+ otherPatient: Patient;
56
+ otherPatientAdmission: InpatientAdmission;
57
+ isOtherPatientTheMother: boolean;
58
+ }
59
+
60
+ const MotherOrChild: React.FC<MotherOrChildProp> = ({
61
+ otherPatient,
62
+ otherPatientAdmission,
63
+ isOtherPatientTheMother,
64
+ }) => {
65
+ const Icon = isOtherPatientTheMother ? MotherIcon : BabyIcon;
66
+
67
+ return (
68
+ <div
69
+ key={otherPatient.uuid}
70
+ className={classNames(styles.motherOrBabyRow, wardPatientCardStyles.wardPatientCardRow)}>
71
+ <div className={styles.motherOrBabyIconDiv}>
72
+ <Icon className={styles.motherOrBabyIcon} size={24} />
73
+ </div>
74
+ <div className={classNames(styles.motherOrBabyRowElementsDiv, wardPatientCardStyles.dotSeparatedChildren)}>
75
+ <WardPatientName patient={otherPatient} />
76
+ <WardPatientIdentifier id="patient-identifier" patient={otherPatient} />
77
+ <WardPatientAge patient={otherPatient} />
78
+ <WardPatientLocation inpatientAdmission={otherPatientAdmission} />
79
+ </div>
80
+ </div>
81
+ );
82
+ };
83
+
84
+ export default MotherChildRow;