@kenyaemr/esm-ward-app 7.0.3-pre.89 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +3 -4
  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,267 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { z } from 'zod';
3
+ import { Controller, useForm } from 'react-hook-form';
4
+ import { zodResolver } from '@hookform/resolvers/zod';
5
+ 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';
14
+ import useWardLocation from '../../hooks/useWardLocation';
15
+ import type { AdmitPatientFormWorkspaceProps } from './types';
16
+ import styles from './admit-patient-form.scss';
17
+
18
+ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
19
+ patient,
20
+ dispositionType,
21
+ closeWorkspace,
22
+ closeWorkspaceWithSavedChanges,
23
+ promptBeforeClosing,
24
+ }) => {
25
+ const { t } = useTranslation();
26
+ const { location } = useWardLocation();
27
+ const { currentProvider } = useSession();
28
+ const [isSubmitting, setIsSubmitting] = useState(false);
29
+ const { mutate: mutateInpatientRequest } = useInpatientRequest();
30
+ const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
31
+ 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
+ }, []);
43
+
44
+ const zodSchema = useMemo(
45
+ () =>
46
+ z.object({
47
+ bedId: z.number().optional(),
48
+ }),
49
+ [isBedManagementModuleInstalled, beds],
50
+ );
51
+
52
+ type FormValues = z.infer<typeof zodSchema>;
53
+
54
+ const {
55
+ control,
56
+ formState: { errors, isDirty },
57
+ handleSubmit,
58
+ getValues,
59
+ watch,
60
+ } = useForm<FormValues>({
61
+ resolver: zodResolver(zodSchema),
62
+ });
63
+
64
+ useEffect(() => {
65
+ promptBeforeClosing(() => isDirty);
66
+ }, [isDirty]);
67
+
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);
95
+ }
96
+ return response;
97
+ }
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();
140
+ }
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
+ );
162
+
163
+ const onError = useCallback((values) => {
164
+ setShowErrorNotifications(true);
165
+ setIsSubmitting(false);
166
+ }, []);
167
+
168
+ return (
169
+ <Form control={control} className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
170
+ <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
+ <Row>
186
+ <Column>
187
+ <h2 className={styles.productiveHeading02}>{t('selectABed', 'Select a bed')}</h2>
188
+ <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
+ )}
236
+ </div>
237
+ </Column>
238
+ </Row>
239
+ <div className={styles.errorNotifications}>
240
+ {showErrorNotifications &&
241
+ Object.entries(errors).map(([key, value]) => {
242
+ return (
243
+ <Row key={key}>
244
+ <Column>
245
+ <InlineNotification kind="error" subtitle={value.message} lowContrast />
246
+ </Column>
247
+ </Row>
248
+ );
249
+ })}
250
+ </div>
251
+ </div>
252
+ <ButtonSet className={styles.buttonSet}>
253
+ <Button size="xl" kind="secondary" onClick={() => closeWorkspace({ ignoreChanges: true })}>
254
+ {t('cancel', 'Cancel')}
255
+ </Button>
256
+ <Button
257
+ type="submit"
258
+ size="xl"
259
+ disabled={isSubmitting || isLoadingEmrConfiguration || errorFetchingEmrConfiguration}>
260
+ {!isSubmitting ? t('admit', 'Admit') : t('admitting', 'Admitting...')}
261
+ </Button>
262
+ </ButtonSet>
263
+ </Form>
264
+ );
265
+ };
266
+
267
+ export default AdmitPatientFormWorkspace;
@@ -0,0 +1,7 @@
1
+ import type { DefaultWorkspaceProps, Patient } from '@openmrs/esm-framework';
2
+ import type { DispositionType } from '../../types';
3
+
4
+ export interface AdmitPatientFormWorkspaceProps extends DefaultWorkspaceProps {
5
+ patient: Patient;
6
+ dispositionType: DispositionType;
7
+ }
@@ -0,0 +1,29 @@
1
+ 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';
5
+ 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
+
9
+ const WardPatientWorkspaceBanner = (wardPatient: WardPatient) => {
10
+ const { headerRowElements } = useCurrentWardCardConfig();
11
+ const { patient, bed, visit } = wardPatient;
12
+
13
+ if (!(patient && visit)) {
14
+ console.warn('Patient details and visit details were not received by the workspace');
15
+ return null;
16
+ }
17
+
18
+ return (
19
+ <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
+ ))}
25
+ </div>
26
+ );
27
+ };
28
+
29
+ export default WardPatientWorkspaceBanner;
@@ -0,0 +1,23 @@
1
+ @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .patientBanner {
5
+ @extend .dotSeparatedChildren;
6
+ display: flex;
7
+ flex-wrap: wrap;
8
+ width: 100%;
9
+ padding: layout.$spacing-04;
10
+ background: $ui-01;
11
+ }
12
+
13
+ .dotSeparatedChildren {
14
+ > div:not(div:first-of-type):not(:empty) {
15
+ display: flex;
16
+ align-items: center;
17
+
18
+ &::before {
19
+ content: '·';
20
+ padding: 0 layout.$spacing-02;
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,210 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import styles from './patient-transfer-swap.scss';
3
+ import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
4
+ import { z } from 'zod';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Controller, useForm } from 'react-hook-form';
7
+ import { zodResolver } from '@hookform/resolvers/zod';
8
+ import { filterBeds } from '../../ward-view/ward-view.resource';
9
+ import type { BedLayout, WardPatientWorkspaceProps } from '../../types';
10
+ import { assignPatientToBed, createEncounter } from '../../ward.resource';
11
+ import useEmrConfiguration from '../../hooks/useEmrConfiguration';
12
+ import { showSnackbar, useSession } from '@openmrs/esm-framework';
13
+ import useWardLocation from '../../hooks/useWardLocation';
14
+ import { useInpatientRequest } from '../../hooks/useInpatientRequest';
15
+ import {
16
+ Form,
17
+ ButtonSet,
18
+ Button,
19
+ RadioButtonGroup,
20
+ RadioButton,
21
+ RadioButtonSkeleton,
22
+ InlineNotification,
23
+ } from '@carbon/react';
24
+
25
+ export default function PatientBedSwapForm({
26
+ promptBeforeClosing,
27
+ closeWorkspaceWithSavedChanges,
28
+ wardPatient,
29
+ }: WardPatientWorkspaceProps) {
30
+ const { patient } = wardPatient;
31
+ const { t } = useTranslation();
32
+ const [showErrorNotifications, setShowErrorNotifications] = useState(false);
33
+ const { isLoading, admissionLocation } = useAdmissionLocation();
34
+ const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
35
+ const [isSubmitting, setIsSubmitting] = useState(false);
36
+ const { currentProvider } = useSession();
37
+ const { location } = useWardLocation();
38
+ const { mutate: mutateAdmissionLocation } = useAdmissionLocation();
39
+ const { mutate: mutateInpatientRequest } = useInpatientRequest();
40
+
41
+ const zodSchema = useMemo(
42
+ () =>
43
+ z.object({
44
+ bedId: z.number({
45
+ required_error: t('pleaseSelectBed', 'Please select a bed'),
46
+ }),
47
+ }),
48
+ [t],
49
+ );
50
+
51
+ type FormValues = z.infer<typeof zodSchema>;
52
+
53
+ const {
54
+ formState: { errors, isDirty },
55
+ control,
56
+ handleSubmit,
57
+ getValues,
58
+ } = useForm<FormValues>({ resolver: zodResolver(zodSchema) });
59
+
60
+ useEffect(() => {
61
+ promptBeforeClosing(() => isDirty);
62
+ return () => promptBeforeClosing(null);
63
+ }, [isDirty]);
64
+
65
+ const getBedInformation = useCallback(
66
+ (bed: BedLayout) => {
67
+ const patients = bed.patients.map((bedPatient) => bedPatient?.person?.preferredName?.display);
68
+ const bedNumber = bed.bedNumber;
69
+ return [bedNumber, ...(patients.length ? patients : [t('empty', 'Empty')])].join(' · ');
70
+ },
71
+ [t],
72
+ );
73
+
74
+ const beds = useMemo(() => (admissionLocation ? filterBeds(admissionLocation) : []), [admissionLocation]);
75
+ const bedDetails = useMemo(
76
+ () =>
77
+ beds.map((bed) => {
78
+ const isPatientAssignedToBed = bed.patients.find((bedPatient) => bedPatient.uuid === patient.uuid);
79
+ return { id: bed.bedId, label: getBedInformation(bed), isPatientAssignedToBed };
80
+ }),
81
+ [beds, getBedInformation],
82
+ );
83
+
84
+ const onSubmit = useCallback(
85
+ (values: FormValues) => {
86
+ const bedSelected = beds.find((bed) => bed.bedId === values.bedId);
87
+ setShowErrorNotifications(false);
88
+ createEncounter({
89
+ patient: patient.uuid,
90
+ encounterType: emrConfiguration.transferWithinHospitalEncounterType.uuid,
91
+ location: location?.uuid,
92
+ encounterProviders: [
93
+ {
94
+ provider: currentProvider?.uuid,
95
+ encounterRole: emrConfiguration.clinicianEncounterRole.uuid,
96
+ },
97
+ ],
98
+ obs: [],
99
+ })
100
+ .then(async (response) => {
101
+ if (response.ok) {
102
+ return assignPatientToBed(values.bedId, patient.uuid, response.data.uuid);
103
+ }
104
+ })
105
+ .then((response) => {
106
+ if (response && response?.ok) {
107
+ showSnackbar({
108
+ kind: 'success',
109
+ title: t('patientAssignedNewbed', 'Patient assigned to new bed'),
110
+ subtitle: t('patientAssignedToBed', '{{patientName}} assigned to bed {{bedNumber}}', {
111
+ patientName: patient.person.preferredName.display,
112
+ bedNumber: bedSelected.bedNumber,
113
+ }),
114
+ });
115
+ mutateAdmissionLocation();
116
+ mutateInpatientRequest();
117
+ closeWorkspaceWithSavedChanges();
118
+ }
119
+ })
120
+ .catch((error: Error) => {
121
+ showSnackbar({
122
+ kind: 'error',
123
+ title: t('errorAssigningBedToPatient', 'Error assigning bed to patient'),
124
+ subtitle: error?.message,
125
+ });
126
+ mutateAdmissionLocation();
127
+ mutateInpatientRequest();
128
+ closeWorkspaceWithSavedChanges();
129
+ })
130
+ .finally(() => {
131
+ setIsSubmitting(false);
132
+ });
133
+ },
134
+ [setShowErrorNotifications, patient, emrConfiguration, currentProvider, location, beds],
135
+ );
136
+
137
+ const onError = useCallback(() => {
138
+ setShowErrorNotifications(true);
139
+ }, []);
140
+
141
+ return (
142
+ <Form onSubmit={handleSubmit(onSubmit, onError)} className={styles.formContainer}>
143
+ <div>
144
+ {errorFetchingEmrConfiguration && (
145
+ <div className={styles.formError}>
146
+ <InlineNotification
147
+ kind="error"
148
+ title={t('somePartsOfTheFormDidntLoad', "Some parts of the form didn't load")}
149
+ subtitle={t(
150
+ 'fetchingEmrConfigurationFailed',
151
+ 'Fetching EMR configuration failed. Try refreshing the page or contact your system administrator.',
152
+ )}
153
+ lowContrast
154
+ hideCloseButton
155
+ />
156
+ </div>
157
+ )}
158
+ <h2 className={styles.productiveHeading02}>{t('selectABed', 'Select a bed')}</h2>
159
+ {isLoading ? (
160
+ <RadioButtonGroup className={styles.radioButtonGroup} name="bedId">
161
+ <RadioButtonSkeleton />
162
+ <RadioButtonSkeleton />
163
+ <RadioButtonSkeleton />
164
+ </RadioButtonGroup>
165
+ ) : (
166
+ <Controller
167
+ name="bedId"
168
+ control={control}
169
+ render={({ field: { onChange, value } }) => (
170
+ <RadioButtonGroup
171
+ className={styles.radioButtonGroup}
172
+ onChange={onChange}
173
+ invalid={!!errors?.bedId?.message}
174
+ invalidText={errors?.bedId?.message}>
175
+ {bedDetails.map(({ id, label, isPatientAssignedToBed }) => (
176
+ <RadioButton
177
+ key={id}
178
+ labelText={label}
179
+ control={control}
180
+ value={id}
181
+ checked={id === value}
182
+ disabled={isPatientAssignedToBed}
183
+ />
184
+ ))}
185
+ </RadioButtonGroup>
186
+ )}
187
+ />
188
+ )}
189
+ {showErrorNotifications && (
190
+ <div className={styles.notifications}>
191
+ {Object.values(errors).map((error) => (
192
+ <InlineNotification lowContrast subtitle={error.message} />
193
+ ))}
194
+ </div>
195
+ )}
196
+ </div>
197
+ <ButtonSet className={styles.buttonSet}>
198
+ <Button size="xl" kind="secondary" onClick={closeWorkspaceWithSavedChanges}>
199
+ {t('cancel', 'Cancel')}
200
+ </Button>
201
+ <Button
202
+ type="submit"
203
+ size="xl"
204
+ disabled={isLoadingEmrConfiguration || isSubmitting || errorFetchingEmrConfiguration}>
205
+ {t('save', 'Save')}
206
+ </Button>
207
+ </ButtonSet>
208
+ </Form>
209
+ );
210
+ }