@kenyaemr/esm-ward-app 8.0.1-pre.95 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/.turbo/turbo-build.log +20 -24
  2. package/dist/109.js +1 -0
  3. package/dist/109.js.map +1 -0
  4. package/dist/124.js +1 -0
  5. package/dist/124.js.map +1 -0
  6. package/dist/125.js +1 -0
  7. package/dist/125.js.map +1 -0
  8. package/dist/130.js +1 -1
  9. package/dist/130.js.LICENSE.txt +2 -0
  10. package/dist/130.js.map +1 -1
  11. package/dist/146.js +1 -0
  12. package/dist/146.js.map +1 -0
  13. package/dist/15.js +1 -0
  14. package/dist/15.js.map +1 -0
  15. package/dist/153.js +1 -0
  16. package/dist/153.js.map +1 -0
  17. package/dist/303.js +2 -1
  18. package/dist/303.js.map +1 -1
  19. package/dist/325.js +1 -0
  20. package/dist/325.js.map +1 -0
  21. package/dist/372.js +2 -0
  22. package/dist/372.js.map +1 -0
  23. package/dist/471.js +1 -0
  24. package/dist/471.js.map +1 -0
  25. package/dist/481.js +1 -0
  26. package/dist/481.js.map +1 -0
  27. package/dist/53.js +1 -0
  28. package/dist/53.js.map +1 -0
  29. package/dist/{960.js → 559.js} +1 -1
  30. package/dist/559.js.map +1 -0
  31. package/dist/574.js +1 -1
  32. package/dist/576.js +1 -0
  33. package/dist/576.js.map +1 -0
  34. package/dist/577.js +1 -1
  35. package/dist/577.js.map +1 -1
  36. package/dist/{255.js → 649.js} +2 -2
  37. package/dist/649.js.LICENSE.txt +9 -0
  38. package/dist/649.js.map +1 -0
  39. package/dist/{659.js → 662.js} +1 -1
  40. package/dist/662.js.map +1 -0
  41. package/dist/920.js +1 -0
  42. package/dist/920.js.map +1 -0
  43. package/dist/921.js +1 -0
  44. package/dist/921.js.map +1 -0
  45. package/dist/922.js +1 -0
  46. package/dist/922.js.map +1 -0
  47. package/dist/969.js +1 -0
  48. package/dist/969.js.map +1 -0
  49. package/dist/kenyaemr-esm-ward-app.js +1 -1
  50. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +304 -128
  51. package/dist/kenyaemr-esm-ward-app.js.map +1 -1
  52. package/dist/main.js +1 -1
  53. package/dist/main.js.LICENSE.txt +0 -10
  54. package/dist/main.js.map +1 -1
  55. package/dist/routes.json +1 -1
  56. package/mock.tsx +62 -0
  57. package/package-lock.json +5001 -0
  58. package/package.json +2 -3
  59. package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +37 -0
  60. package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +20 -0
  61. package/src/beds/empty-bed-skeleton.tsx +4 -3
  62. package/src/beds/empty-bed.component.tsx +3 -3
  63. package/src/beds/ward-bed.component.tsx +41 -0
  64. package/src/beds/ward-bed.scss +45 -0
  65. package/src/beds/{occupied-bed.test.tsx → ward-bed.test.tsx} +42 -20
  66. package/src/config-schema.ts +203 -84
  67. package/src/constant.ts +1 -1
  68. package/src/hooks/useAdmissionLocation.ts +22 -4
  69. package/src/hooks/useAssignedBedByPatient.ts +9 -0
  70. package/src/hooks/useBeds.ts +3 -4
  71. package/src/hooks/useConcept.ts +3 -4
  72. package/src/hooks/useEmrConfiguration.ts +5 -0
  73. package/src/hooks/useInpatientAdmission.ts +9 -14
  74. package/src/hooks/useInpatientRequest.ts +4 -15
  75. package/src/hooks/useLocations.ts +8 -51
  76. package/src/hooks/useMotherAndChildren.ts +46 -0
  77. package/src/hooks/useObs.ts +3 -7
  78. package/src/hooks/usePatientPendingOrders.ts +16 -0
  79. package/src/hooks/useWardPatientGrouping.ts +32 -0
  80. package/src/index.ts +45 -17
  81. package/src/location-selector/location-selector.component.tsx +18 -21
  82. package/src/root.component.tsx +3 -0
  83. package/src/routes.json +41 -3
  84. package/src/types/index.ts +50 -1
  85. package/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx +38 -0
  86. package/src/ward-patient-card/card-rows/coded-obs-tags-row.component.tsx +108 -0
  87. package/src/ward-patient-card/card-rows/mother-child-row.component.tsx +84 -0
  88. package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
  89. package/src/ward-patient-card/card-rows/pending-items-row.component.tsx +54 -0
  90. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +1 -1
  91. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +62 -39
  92. package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +5 -5
  93. package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +18 -41
  94. package/src/ward-patient-card/row-elements/ward-patient-location.tsx +19 -0
  95. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +38 -34
  96. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +26 -13
  97. package/src/ward-patient-card/row-elements/ward-patient-pending-order.component.tsx +45 -0
  98. package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.tsx +38 -0
  99. package/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx +32 -0
  100. package/src/ward-patient-card/row-elements/ward-patient-skeleton-text.tsx +9 -0
  101. package/src/ward-patient-card/ward-patient-card.component.tsx +14 -45
  102. package/src/ward-patient-card/ward-patient-card.scss +68 -9
  103. package/src/ward-view/default-ward/default-ward-beds.component.tsx +42 -0
  104. package/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx +32 -0
  105. package/src/ward-view/default-ward/default-ward-patient-card.component.tsx +31 -0
  106. package/src/ward-view/default-ward/default-ward-pending-patients.component.tsx +52 -0
  107. package/src/ward-view/default-ward/default-ward-unassigned-patients.component.tsx +32 -0
  108. package/src/ward-view/default-ward/default-ward-view.component.tsx +31 -0
  109. package/src/ward-view/materal-ward/maternal-ward-beds.component.tsx +65 -0
  110. package/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx +30 -0
  111. package/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx +93 -0
  112. package/src/{beds/occupied-bed.scss → ward-view/materal-ward/maternal-ward-patient-card.scss} +4 -9
  113. package/src/ward-view/materal-ward/maternal-ward-patient-card.test.tsx +58 -0
  114. package/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx +48 -0
  115. package/src/ward-view/materal-ward/maternal-ward-unassigned-patients.component.tsx +33 -0
  116. package/src/ward-view/materal-ward/maternal-ward-view.component.tsx +38 -0
  117. package/src/ward-view/materal-ward/maternal-ward-view.resource.ts +89 -0
  118. package/src/ward-view/ward-view.component.tsx +15 -163
  119. package/src/ward-view/ward-view.resource.ts +193 -1
  120. package/src/ward-view/ward-view.scss +17 -6
  121. package/src/ward-view/ward-view.test.tsx +43 -48
  122. package/src/ward-view/ward.component.tsx +106 -0
  123. package/src/ward-view-header/admission-requests-bar.component.tsx +14 -9
  124. package/src/ward-view-header/admission-requests-bar.test.tsx +11 -23
  125. package/src/ward-view-header/admission-requests.scss +1 -1
  126. package/src/ward-view-header/ward-metric.component.tsx +24 -0
  127. package/src/ward-view-header/ward-metric.scss +25 -0
  128. package/src/ward-view-header/ward-metrics.component.tsx +78 -0
  129. package/src/ward-view-header/ward-metrics.scss +7 -0
  130. package/src/ward-view-header/ward-metrics.test.tsx +37 -0
  131. package/src/ward-view-header/ward-view-header.component.tsx +9 -4
  132. package/src/ward-view-header/ward-view-header.scss +0 -1
  133. package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +70 -6
  134. package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +10 -23
  135. package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +9 -3
  136. package/src/ward-workspace/admission-request-card/admission-request-card.scss +13 -4
  137. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +55 -33
  138. package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +30 -37
  139. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +98 -203
  140. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +116 -180
  141. package/src/ward-workspace/bed-selector.component.tsx +119 -0
  142. package/src/ward-workspace/bed-selector.scss +15 -0
  143. package/src/ward-workspace/patient-banner/patient-banner.component.tsx +7 -14
  144. package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +23 -0
  145. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient-action-button.extension.tsx +2 -1
  146. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.workspace.tsx +18 -9
  147. package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
  148. package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +113 -0
  149. package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +68 -79
  150. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +24 -24
  151. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +12 -2
  152. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +12 -8
  153. package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +11 -0
  154. package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +1 -1
  155. package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +1 -1
  156. package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +2 -2
  157. package/src/ward-workspace/ward-patient-notes/notes.resource.ts +5 -7
  158. package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +1 -1
  159. package/src/ward-workspace/ward-patient-notes/types.ts +0 -4
  160. package/src/ward.resource.ts +38 -2
  161. package/translations/en.json +31 -7
  162. package/dist/152.js +0 -1
  163. package/dist/152.js.map +0 -1
  164. package/dist/255.js.map +0 -1
  165. package/dist/269.js +0 -1
  166. package/dist/269.js.map +0 -1
  167. package/dist/346.js +0 -1
  168. package/dist/346.js.map +0 -1
  169. package/dist/466.js +0 -1
  170. package/dist/466.js.map +0 -1
  171. package/dist/659.js.map +0 -1
  172. package/dist/729.js +0 -1
  173. package/dist/729.js.map +0 -1
  174. package/dist/749.js +0 -1
  175. package/dist/749.js.map +0 -1
  176. package/dist/76.js +0 -1
  177. package/dist/76.js.map +0 -1
  178. package/dist/793.js +0 -2
  179. package/dist/793.js.map +0 -1
  180. package/dist/803.js +0 -1
  181. package/dist/803.js.map +0 -1
  182. package/dist/960.js.map +0 -1
  183. package/src/beds/empty-bed.scss +0 -28
  184. package/src/beds/occupied-bed.component.tsx +0 -35
  185. package/src/beds/unassigned-patient.component.tsx +0 -20
  186. package/src/beds/unassigned-patient.scss +0 -6
  187. package/src/config-schema-admission-request-note.ts +0 -9
  188. package/src/config-schema-extension-colored-obs-tags.ts +0 -91
  189. package/src/hooks/useCurrentWardCardConfig.ts +0 -32
  190. package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +0 -27
  191. package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +0 -13
  192. package/src/ward-patient-card/ward-patient-card-element.component.tsx +0 -65
  193. package/src/ward-view/ward-bed.component.tsx +0 -14
  194. /package/dist/{793.js.LICENSE.txt → 303.js.LICENSE.txt} +0 -0
  195. /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
  196. /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
@@ -1,20 +1,24 @@
1
+ import { Button, ButtonSet, Column, Form, InlineNotification, Row } from '@carbon/react';
2
+ import { zodResolver } from '@hookform/resolvers/zod';
3
+ import { showSnackbar, useAppContext } from '@openmrs/esm-framework';
1
4
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { z } from 'zod';
3
5
  import { Controller, useForm } from 'react-hook-form';
4
- import { zodResolver } from '@hookform/resolvers/zod';
5
6
  import { useTranslation } from 'react-i18next';
6
- import { Button, ButtonSet, Column, Dropdown, DropdownSkeleton, Form, InlineNotification, Row } from '@carbon/react';
7
- import { showSnackbar, useFeatureFlag, useSession } from '@openmrs/esm-framework';
8
- import { filterBeds } from '../../ward-view/ward-view.resource';
9
- import type { BedLayout, DispositionType } from '../../types';
10
- import { assignPatientToBed, createEncounter } from '../../ward.resource';
11
- import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
12
- import { useInpatientRequest } from '../../hooks/useInpatientRequest';
13
- import useEmrConfiguration from '../../hooks/useEmrConfiguration';
7
+ import { z } from 'zod';
8
+ import { useAssignedBedByPatient } from '../../hooks/useAssignedBedByPatient';
14
9
  import useWardLocation from '../../hooks/useWardLocation';
15
- import type { AdmitPatientFormWorkspaceProps } from './types';
10
+ import type { WardViewContext } from '../../types';
11
+ import { assignPatientToBed, removePatientFromBed, useAdmitPatient } from '../../ward.resource';
12
+ import BedSelector from '../bed-selector.component';
16
13
  import styles from './admit-patient-form.scss';
14
+ import type { AdmitPatientFormWorkspaceProps } from './types';
17
15
 
16
+ /**
17
+ * This form gets rendered when the user clicks "admit patient" in
18
+ * the patient card in the admission requests workspace, but only when
19
+ * the bed management module is installed. It asks to (optionally) select
20
+ * a bed to assign to patient
21
+ */
18
22
  const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
19
23
  patient,
20
24
  dispositionType,
@@ -24,29 +28,23 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
24
28
  }) => {
25
29
  const { t } = useTranslation();
26
30
  const { location } = useWardLocation();
27
- const { currentProvider } = useSession();
28
31
  const [isSubmitting, setIsSubmitting] = useState(false);
29
- const { mutate: mutateInpatientRequest } = useInpatientRequest();
30
- const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
32
+ const { admitPatient, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useAdmitPatient();
31
33
  const [showErrorNotifications, setShowErrorNotifications] = useState(false);
32
- const { isLoading, admissionLocation, mutate: mutateAdmissionLocation } = useAdmissionLocation();
33
- const beds = useMemo(() => (isLoading ? [] : filterBeds(admissionLocation)), [admissionLocation]);
34
- const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
35
- const getBedRepresentation = useCallback((bedLayout: BedLayout) => {
36
- const bedNumber = bedLayout.bedNumber;
37
- const patients =
38
- bedLayout.patients.length === 0
39
- ? [t('emptyText', 'Empty')]
40
- : bedLayout.patients.map((patient) => patient?.person?.preferredName?.display);
41
- return [bedNumber, ...patients].join(' · ');
42
- }, []);
34
+ const { wardPatientGroupDetails } = useAppContext<WardViewContext>('ward-view-context') ?? {};
35
+ const { isLoading } = wardPatientGroupDetails?.admissionLocationResponse ?? {};
36
+
37
+ const { data: bedsAssignedToPatient, isLoading: isLoadingBedsAssignedToPatient } = useAssignedBedByPatient(
38
+ patient.uuid,
39
+ );
40
+ const beds = isLoading ? [] : wardPatientGroupDetails?.bedLayouts ?? [];
43
41
 
44
42
  const zodSchema = useMemo(
45
43
  () =>
46
44
  z.object({
47
45
  bedId: z.number().optional(),
48
46
  }),
49
- [isBedManagementModuleInstalled, beds],
47
+ [beds],
50
48
  );
51
49
 
52
50
  type FormValues = z.infer<typeof zodSchema>;
@@ -55,8 +53,6 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
55
53
  control,
56
54
  formState: { errors, isDirty },
57
55
  handleSubmit,
58
- getValues,
59
- watch,
60
56
  } = useForm<FormValues>({
61
57
  resolver: zodResolver(zodSchema),
62
58
  });
@@ -65,174 +61,108 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
65
61
  promptBeforeClosing(() => isDirty);
66
62
  }, [isDirty]);
67
63
 
68
- const onSubmit = useCallback(
69
- (values: FormValues) => {
70
- setShowErrorNotifications(false);
71
- setIsSubmitting(true);
72
- const bedSelected = beds.find((bed) => bed.bedId === values.bedId);
73
- createEncounter({
74
- patient: patient.uuid,
75
- encounterType:
76
- dispositionType === 'ADMIT'
77
- ? emrConfiguration.admissionEncounterType.uuid
78
- : dispositionType === 'TRANSFER'
79
- ? emrConfiguration.transferWithinHospitalEncounterType.uuid
80
- : null,
81
- location: location?.uuid,
82
- encounterProviders: [
83
- {
84
- provider: currentProvider?.uuid,
85
- encounterRole: emrConfiguration.clinicianEncounterRole.uuid,
86
- },
87
- ],
88
- obs: [],
89
- })
90
- .then(
91
- async (response) => {
92
- if (response.ok) {
93
- if (bedSelected) {
94
- return assignPatientToBed(values.bedId, patient.uuid, response.data.uuid);
64
+ const onSubmit = (values: FormValues) => {
65
+ setShowErrorNotifications(false);
66
+ setIsSubmitting(true);
67
+ const bedSelected = beds.find((bed) => bed.bedId === values.bedId);
68
+ admitPatient(patient, dispositionType)
69
+ .then(
70
+ async (response) => {
71
+ if (response.ok) {
72
+ if (bedSelected) {
73
+ return assignPatientToBed(values.bedId, patient.uuid, response.data.uuid);
74
+ } else {
75
+ const assignedBedId = bedsAssignedToPatient?.data?.results?.[0]?.bedId;
76
+ if (assignedBedId) {
77
+ return removePatientFromBed(assignedBedId, patient.uuid);
95
78
  }
96
79
  return response;
97
80
  }
98
- },
99
- (err: Error) => {
100
- showSnackbar({
101
- kind: 'error',
102
- title: t('errorCreatingEncounter', 'Failed to admit patient', {
103
- encounterType:
104
- dispositionType === 'ADMIT'
105
- ? emrConfiguration.admissionEncounterType.display
106
- : emrConfiguration.transferWithinHospitalEncounterType.display,
107
- }),
108
- subtitle: err.message,
109
- });
110
- },
111
- )
112
- .then(
113
- (response) => {
114
- if (response && response?.ok) {
115
- if (bedSelected) {
116
- showSnackbar({
117
- kind: 'success',
118
- title: t('patientAdmittedSuccessfully', 'Patient admitted successfully'),
119
- subtitle: t(
120
- 'patientAdmittedSuccessfullySubtitle',
121
- '{{patientName}} has been successfully admitted and assigned to bed {{bedNumber}}',
122
- {
123
- patientName: patient.person.preferredName.display,
124
- bedNumber: bedSelected.bedNumber,
125
- },
126
- ),
127
- });
128
- } else {
129
- showSnackbar({
130
- kind: 'success',
131
- title: t('patientAdmittedSuccessfully', 'Patient admitted successfully'),
132
- subtitle: t('patientAdmittedWoBed', 'Patient admitted successfully to {{location}}', {
133
- location: location?.display,
134
- }),
135
- });
136
- }
137
- mutateAdmissionLocation();
138
- mutateInpatientRequest();
139
- closeWorkspaceWithSavedChanges();
81
+ }
82
+ },
83
+ (err: Error) => {
84
+ showSnackbar({
85
+ kind: 'error',
86
+ title: t('errorCreatingEncounter', 'Failed to admit patient'),
87
+ subtitle: err.message,
88
+ });
89
+ },
90
+ )
91
+ .then(
92
+ (response) => {
93
+ if (response && response?.ok) {
94
+ if (bedSelected) {
95
+ showSnackbar({
96
+ kind: 'success',
97
+ title: t('patientAdmittedSuccessfully', 'Patient admitted successfully'),
98
+ subtitle: t(
99
+ 'patientAdmittedSuccessfullySubtitle',
100
+ '{{patientName}} has been successfully admitted and assigned to bed {{bedNumber}}',
101
+ {
102
+ patientName: patient.person.preferredName.display,
103
+ bedNumber: bedSelected.bedNumber,
104
+ },
105
+ ),
106
+ });
107
+ } else {
108
+ showSnackbar({
109
+ kind: 'success',
110
+ title: t('patientAdmittedSuccessfully', 'Patient admitted successfully'),
111
+ subtitle: t('patientAdmittedWoBed', 'Patient admitted successfully to {{location}}', {
112
+ location: location?.display,
113
+ }),
114
+ });
140
115
  }
141
- },
142
- () => {
143
- showSnackbar({
144
- kind: 'warning',
145
- title: t('patientAdmittedSuccessfully', 'Patient admitted successfully'),
146
- subtitle: t(
147
- 'patientAdmittedButBedNotAssigned',
148
- 'Patient admitted successfully but fail to assign bed to patient',
149
- ),
150
- });
151
- mutateAdmissionLocation();
152
- mutateInpatientRequest();
153
- closeWorkspaceWithSavedChanges();
154
- },
155
- )
156
- .finally(() => {
157
- setIsSubmitting(false);
158
- });
159
- },
160
- [beds, patient, emrConfiguration, location, closeWorkspaceWithSavedChanges, dispositionType, currentProvider],
161
- );
116
+ }
117
+ },
118
+ () => {
119
+ showSnackbar({
120
+ kind: 'warning',
121
+ title: t('patientAdmittedSuccessfully', 'Patient admitted successfully'),
122
+ subtitle: t(
123
+ 'patientAdmittedButBedNotAssigned',
124
+ 'Patient admitted successfully but fail to assign bed to patient',
125
+ ),
126
+ });
127
+ },
128
+ )
129
+ .finally(() => {
130
+ setIsSubmitting(false);
131
+ wardPatientGroupDetails?.mutate?.();
132
+ closeWorkspaceWithSavedChanges();
133
+ });
134
+ };
162
135
 
163
136
  const onError = useCallback((values) => {
164
137
  setShowErrorNotifications(true);
165
138
  setIsSubmitting(false);
166
139
  }, []);
167
140
 
141
+ if (!wardPatientGroupDetails) return <></>;
168
142
  return (
169
143
  <Form control={control} className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
170
144
  <div className={styles.formContent}>
171
- {errorFetchingEmrConfiguration && (
172
- <div className={styles.formError}>
173
- <InlineNotification
174
- kind="error"
175
- title={t('somePartsOfTheFormDidntLoad', "Some parts of the form didn't load")}
176
- subtitle={t(
177
- 'fetchingEmrConfigurationFailed',
178
- 'Fetching EMR configuration failed. Try refreshing the page or contact your system administrator.',
179
- )}
180
- lowContrast
181
- hideCloseButton
182
- />
183
- </div>
184
- )}
185
145
  <Row>
186
146
  <Column>
187
147
  <h2 className={styles.productiveHeading02}>{t('selectABed', 'Select a bed')}</h2>
188
148
  <div className={styles.bedSelectionDropdown}>
189
- {isBedManagementModuleInstalled ? (
190
- isLoading ? (
191
- <DropdownSkeleton />
192
- ) : beds.length ? (
193
- <Controller
194
- control={control}
195
- name="bedId"
196
- render={({ field: { onChange }, fieldState: { error } }) => {
197
- const selectedItem = beds.find((bed) => bed.bedId === getValues()?.bedId);
198
- return (
199
- <Dropdown
200
- id="default"
201
- titleText=""
202
- helperText=""
203
- label={!selectedItem && t('chooseAnOption', 'Choose an option')}
204
- items={beds}
205
- itemToString={getBedRepresentation}
206
- selectedItem={selectedItem}
207
- onChange={({ selectedItem }: { selectedItem: BedLayout }) => onChange(selectedItem.bedId)}
208
- invalid={!!error}
209
- invalidText={error?.message}
210
- />
211
- );
212
- }}
213
- />
214
- ) : (
215
- <InlineNotification
216
- kind="error"
217
- title={t('noBedsConfiguredForLocation', 'No beds configured for {{location}} location', {
218
- location: location?.display,
219
- })}
220
- lowContrast
221
- hideCloseButton
222
- />
223
- )
224
- ) : (
225
- <InlineNotification
226
- kind="info"
227
- title={t('unableToSelectBeds', 'Unable to select beds')}
228
- subtitle={t(
229
- 'bedManagementModuleNotInstalled',
230
- 'Bed management module is not present to allow bed selection',
231
- )}
232
- lowContrast
233
- hideCloseButton
234
- />
235
- )}
149
+ <Controller
150
+ control={control}
151
+ name="bedId"
152
+ render={({ field: { onChange, value }, fieldState: { error } }) => {
153
+ return (
154
+ <BedSelector
155
+ beds={beds}
156
+ isLoadingBeds={isLoading}
157
+ currentPatient={patient}
158
+ selectedBedId={value}
159
+ error={error}
160
+ control={control}
161
+ onChange={onChange}
162
+ />
163
+ );
164
+ }}
165
+ />
236
166
  </div>
237
167
  </Column>
238
168
  </Row>
@@ -256,7 +186,13 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
256
186
  <Button
257
187
  type="submit"
258
188
  size="xl"
259
- disabled={isSubmitting || isLoadingEmrConfiguration || errorFetchingEmrConfiguration}>
189
+ disabled={
190
+ isSubmitting ||
191
+ isLoadingEmrConfiguration ||
192
+ errorFetchingEmrConfiguration ||
193
+ isLoading ||
194
+ isLoadingBedsAssignedToPatient
195
+ }>
260
196
  {!isSubmitting ? t('admit', 'Admit') : t('admitting', 'Admitting...')}
261
197
  </Button>
262
198
  </ButtonSet>
@@ -0,0 +1,119 @@
1
+ import { Dropdown, InlineNotification, RadioButton, RadioButtonGroup, RadioButtonSkeleton } from '@carbon/react';
2
+ import { type Patient } from '@openmrs/esm-framework';
3
+ import React from 'react';
4
+ import { type Control, type FieldError } from 'react-hook-form';
5
+ import { useTranslation } from 'react-i18next';
6
+ import useWardLocation from '../hooks/useWardLocation';
7
+ import { type BedLayout } from '../types';
8
+ import styles from './bed-selector.scss';
9
+
10
+ interface BedSelectorProps {
11
+ beds: BedLayout[];
12
+ isLoadingBeds: boolean;
13
+ currentPatient: Patient;
14
+ selectedBedId: number;
15
+ error: FieldError;
16
+ onChange(bedId: number);
17
+ control: Control<{ bedId?: number }, any, { bedId?: number }>;
18
+ minBedCountToUseDropdown?: number;
19
+ }
20
+
21
+ interface BedDropdownItem {
22
+ bedId: number;
23
+ label: string;
24
+ disabled: boolean;
25
+ }
26
+
27
+ const BedSelector: React.FC<BedSelectorProps> = ({
28
+ selectedBedId,
29
+ beds,
30
+ isLoadingBeds,
31
+ error,
32
+ onChange,
33
+ currentPatient,
34
+ control,
35
+ minBedCountToUseDropdown = 16,
36
+ }) => {
37
+ const { location } = useWardLocation();
38
+ const { t } = useTranslation();
39
+
40
+ const getBedRepresentation = (bedLayout: BedLayout) => {
41
+ const bedNumber = bedLayout.bedNumber;
42
+ const patients =
43
+ bedLayout.patients.length === 0
44
+ ? [t('emptyText', 'Empty')]
45
+ : bedLayout.patients.map((patient) => patient?.person?.preferredName?.display);
46
+ return [bedNumber, ...patients].join(' · ');
47
+ };
48
+
49
+ const bedDropdownItems: BedDropdownItem[] = [
50
+ { bedId: 0, label: t('noBed', 'No bed'), disabled: false },
51
+ ...beds.map((bed) => {
52
+ const isPatientAssignedToBed = bed.patients.some((bedPatient) => bedPatient.uuid === currentPatient.uuid);
53
+ return { bedId: bed.bedId, label: getBedRepresentation(bed), disabled: isPatientAssignedToBed };
54
+ }),
55
+ ];
56
+ const selectedItem = bedDropdownItems.find((bed) => bed.bedId === selectedBedId);
57
+
58
+ const useDropdown = beds.length >= minBedCountToUseDropdown;
59
+
60
+ if (isLoadingBeds) {
61
+ return (
62
+ <RadioButtonGroup className={styles.radioButtonGroup} name="bedId">
63
+ <RadioButtonSkeleton />
64
+ <RadioButtonSkeleton />
65
+ <RadioButtonSkeleton />
66
+ </RadioButtonGroup>
67
+ );
68
+ }
69
+ if (!beds.length) {
70
+ return (
71
+ <InlineNotification
72
+ kind="error"
73
+ title={t('noBedsConfiguredForLocation', 'No beds configured for {{location}} location', {
74
+ location: location?.display,
75
+ })}
76
+ lowContrast
77
+ hideCloseButton
78
+ />
79
+ );
80
+ }
81
+ if (useDropdown) {
82
+ return (
83
+ <Dropdown
84
+ id="default"
85
+ titleText=""
86
+ helperText=""
87
+ label={!selectedItem && t('chooseAnOption', 'Choose an option')}
88
+ items={bedDropdownItems}
89
+ itemToString={(bedDropdownItem: BedDropdownItem) => bedDropdownItem.label}
90
+ selectedItem={selectedItem}
91
+ onChange={({ selectedItem }: { selectedItem: BedLayout }) => onChange(selectedItem.bedId)}
92
+ invalid={!!error}
93
+ invalidText={error?.message}
94
+ />
95
+ );
96
+ } else {
97
+ return (
98
+ <RadioButtonGroup
99
+ name="bedId"
100
+ className={styles.radioButtonGroup}
101
+ onChange={onChange}
102
+ invalid={!!error}
103
+ invalidText={error?.message}>
104
+ {bedDropdownItems.map(({ bedId, label, disabled }) => (
105
+ <RadioButton
106
+ key={bedId}
107
+ labelText={label}
108
+ control={control}
109
+ value={bedId}
110
+ checked={bedId === selectedBedId}
111
+ disabled={disabled}
112
+ />
113
+ ))}
114
+ </RadioButtonGroup>
115
+ );
116
+ }
117
+ };
118
+
119
+ export default BedSelector;
@@ -0,0 +1,15 @@
1
+ @use '@carbon/type';
2
+ @use '@carbon/layout';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .radioButtonGroup {
6
+ margin-top: layout.$spacing-03;
7
+ fieldset {
8
+ flex-direction: column;
9
+ align-items: flex-start;
10
+
11
+ :global(.cds--radio-button-wrapper) {
12
+ margin-bottom: layout.$spacing-04;
13
+ }
14
+ }
15
+ }
@@ -1,27 +1,20 @@
1
+ import { useAppContext } from '@openmrs/esm-framework';
1
2
  import React from 'react';
2
- import type { WardPatient } from '../../types';
3
- import { WardPatientCardElement } from '../../ward-patient-card/ward-patient-card-element.component';
4
- import { useCurrentWardCardConfig } from '../../hooks/useCurrentWardCardConfig';
3
+ import type { WardPatient, WardViewContext } from '../../types';
5
4
  import styles from './style.scss';
6
- import WardPatientBedNumber from '../../ward-patient-card/row-elements/ward-patient-bed-number';
7
- import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-name';
8
5
 
9
6
  const WardPatientWorkspaceBanner = (wardPatient: WardPatient) => {
10
- const { headerRowElements } = useCurrentWardCardConfig();
11
- const { patient, bed, visit } = wardPatient;
7
+ const { patient } = wardPatient;
8
+ const {WardPatientHeader} = useAppContext<WardViewContext>('ward-view-context') ?? {};
12
9
 
13
- if (!(patient && visit)) {
14
- console.warn('Patient details and visit details were not received by the workspace');
10
+ if (!patient) {
11
+ console.warn('Patient details were not received by the ward workspace');
15
12
  return null;
16
13
  }
17
14
 
18
15
  return (
19
16
  <div className={styles.patientBanner}>
20
- {bed ? <WardPatientBedNumber bed={bed} /> : null}
21
- <WardPatientName patient={patient} />
22
- {headerRowElements.map((elementId, i) => (
23
- <WardPatientCardElement key={`ward-card-${patient.uuid}-header-${i}`} elementId={elementId} {...wardPatient} />
24
- ))}
17
+ {WardPatientHeader && <WardPatientHeader {...wardPatient} />}
25
18
  </div>
26
19
  );
27
20
  };
@@ -0,0 +1,23 @@
1
+ import React, { useMemo } from 'react';
2
+ import { ExtensionSlot } from '@openmrs/esm-framework';
3
+ import type { WardPatientWorkspaceProps } from '../../types';
4
+
5
+ const WardPatientClinicalFormsWorkspace: React.FC<WardPatientWorkspaceProps> = (props) => {
6
+ const { wardPatient, ...restWorkspaceProps } = props;
7
+ const patientUuid = wardPatient?.patient?.uuid;
8
+
9
+ const clinicalFormsExtensionState = useMemo(
10
+ () => ({
11
+ patientUuid,
12
+ clinicalFormsWorkspaceName: 'ward-patient-clinical-forms-workspace',
13
+ formEntryWorkspaceName: 'ward-patient-form-entry-workspace',
14
+ htmlFormEntryWorkspaceName: 'ward-patient-html-form-entry-workspace',
15
+ ...restWorkspaceProps,
16
+ }),
17
+ [patientUuid, restWorkspaceProps],
18
+ );
19
+
20
+ return <ExtensionSlot name="ward-patient-clinical-forms-workspace-slot" state={clinicalFormsExtensionState} />;
21
+ };
22
+
23
+ export default WardPatientClinicalFormsWorkspace;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { UserAvatarIcon } from '@openmrs/esm-framework';
4
4
  import { ActionMenuButton, launchWorkspace } from '@openmrs/esm-framework';
5
+ import type { WardPatientWorkspaceProps } from '../../types';
5
6
 
6
7
  export default function WardPatientActionButton() {
7
8
  const { t } = useTranslation();
@@ -11,7 +12,7 @@ export default function WardPatientActionButton() {
11
12
  getIcon={(props) => <UserAvatarIcon {...props} />}
12
13
  label={t('Patient', 'patient')}
13
14
  iconDescription={t('Patient', 'patient')}
14
- handler={() => launchWorkspace('ward-patient-workspace')}
15
+ handler={() => launchWorkspace<WardPatientWorkspaceProps>('ward-patient-workspace')}
15
16
  type={'ward'}
16
17
  />
17
18
  );
@@ -1,21 +1,28 @@
1
1
  import { age, attach, ExtensionSlot, type Patient } from '@openmrs/esm-framework';
2
2
  import React, { useEffect } from 'react';
3
- import { type WardPatientWorkspaceProps } from '../types';
4
3
  import styles from './ward-patient.style.scss';
5
4
  import { useTranslation } from 'react-i18next';
6
- import { getGender } from '../ward-patient-card/row-elements/ward-patient-gender.component';
5
+ import { type WardPatientWorkspaceProps } from '../../types';
6
+ import { getGender } from '../../ward-patient-card/row-elements/ward-patient-gender.component';
7
7
 
8
8
  attach('ward-patient-workspace-header-slot', 'patient-vitals-info');
9
9
 
10
- export default function WardPatientWorkspace({ setTitle, wardPatient: { patient } }: WardPatientWorkspaceProps) {
10
+ export default function WardPatientWorkspace({ setTitle, wardPatient }: WardPatientWorkspaceProps) {
11
11
  useEffect(() => {
12
- setTitle(patient.person.display, <PatientWorkspaceTitle patient={patient} />);
13
- }, []);
12
+ if (wardPatient) {
13
+ const { patient } = wardPatient;
14
+ setTitle(patient.person.display, <PatientWorkspaceTitle key={patient.uuid} patient={patient} />);
15
+ }
16
+ }, [wardPatient]);
14
17
 
15
18
  return (
16
- <div className={styles.workspaceContainer}>
17
- <WardPatientWorkspaceView patient={patient} />
18
- </div>
19
+ <>
20
+ {wardPatient && (
21
+ <div className={styles.workspaceContainer}>
22
+ <WardPatientWorkspaceView patient={wardPatient.patient} />
23
+ </div>
24
+ )}
25
+ </>
19
26
  );
20
27
  }
21
28
 
@@ -45,7 +52,9 @@ const PatientWorkspaceTitle: React.FC<WardPatientWorkspaceViewProps> = ({ patien
45
52
  <>
46
53
  <div>{patient.person.display} &nbsp;</div>
47
54
  <div className={styles.headerPatientDetail}>&middot; &nbsp; {getGender(t, patient.person?.gender)}</div>
48
- <div className={styles.headerPatientDetail}>&middot; &nbsp; {age(patient.person?.birthdate)}</div>
55
+ {patient.person?.birthdate && (
56
+ <div className={styles.headerPatientDetail}>&middot; &nbsp; {age(patient.person?.birthdate)}</div>
57
+ )}
49
58
  </>
50
59
  );
51
60
  };
@@ -0,0 +1,41 @@
1
+ @use '@carbon/type';
2
+ @use '@carbon/layout';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .workspaceContent {
6
+ padding: layout.$spacing-05;
7
+ height: 100%;
8
+ display: flex;
9
+ flex-direction: column;
10
+ color: #393939;
11
+ }
12
+
13
+ .patientWorkspaceBanner {
14
+ margin: (-(layout.$spacing-05)) (-(layout.$spacing-05)) layout.$spacing-05 (-(layout.$spacing-05));
15
+ }
16
+
17
+ .contentSwitcher {
18
+ margin-top: layout.$spacing-03;
19
+ }
20
+
21
+ .workspaceForm {
22
+ margin-top: layout.$spacing-05;
23
+ flex: 1;
24
+ }
25
+
26
+ .productiveHeading02 {
27
+ @include type.type-style('heading-compact-02');
28
+ }
29
+
30
+ .buttonSet {
31
+ margin: 0 (-(layout.$spacing-05)) (-(layout.$spacing-05)) (-(layout.$spacing-05));
32
+
33
+ button {
34
+ max-width: unset !important;
35
+ width: auto !important;
36
+
37
+ > svg {
38
+ fill: currentColor !important;
39
+ }
40
+ }
41
+ }