@kenyaemr/esm-ward-app 8.5.1-pre.25 → 8.5.1-pre.33
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.
- package/.turbo/turbo-build.log +11 -11
- package/dist/1352.js +2 -0
- package/dist/1352.js.map +1 -0
- package/dist/1580.js +2 -0
- package/dist/1580.js.map +1 -0
- package/dist/1879.js +1 -1
- package/dist/1879.js.map +1 -1
- package/dist/1917.js +1 -1
- package/dist/2557.js +1 -1
- package/dist/2557.js.map +1 -1
- package/dist/2932.js +1 -1
- package/dist/3365.js +1 -1
- package/dist/3365.js.map +1 -1
- package/dist/3423.js +1 -1
- package/dist/3423.js.map +1 -1
- package/dist/3737.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4430.js +1 -1
- package/dist/465.js +1 -1
- package/dist/465.js.map +1 -1
- package/dist/4743.js +1 -1
- package/dist/6012.js +1 -1
- package/dist/6012.js.map +1 -1
- package/dist/6871.js +1 -1
- package/dist/7179.js +1 -1
- package/dist/7179.js.map +1 -1
- package/dist/7524.js +1 -1
- package/dist/7661.js +1 -1
- package/dist/7661.js.map +1 -1
- package/dist/8205.js +1 -1
- package/dist/9045.js +1 -1
- package/dist/9880.js +1 -1
- package/dist/9880.js.map +1 -1
- package/dist/kenyaemr-esm-ward-app.js +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +93 -93
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +80 -13
- package/src/hooks/useAdmissionLocation.ts +2 -2
- package/src/ward-patients/discharge-in-patients.tsx +2 -2
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.scss +34 -21
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +291 -66
- package/src/ward-workspace/admit-patient-form-workspace/diagnosis-input.component.tsx +42 -0
- package/src/ward-workspace/admit-patient-form-workspace/patient-admission.resources.ts +118 -0
- package/src/ward-workspace/bed-selector.component.tsx +11 -5
- package/src/ward.resource.ts +2 -2
- package/translations/en.json +19 -1
- package/dist/2728.js +0 -2
- package/dist/2728.js.map +0 -1
- package/dist/2775.js +0 -2
- package/dist/2775.js.map +0 -1
- /package/dist/{2728.js.LICENSE.txt → 1352.js.LICENSE.txt} +0 -0
- /package/dist/{2775.js.LICENSE.txt → 1580.js.LICENSE.txt} +0 -0
|
@@ -1,17 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
ButtonSet,
|
|
4
|
+
Column,
|
|
5
|
+
ComboBox,
|
|
6
|
+
DatePicker,
|
|
7
|
+
DatePickerInput,
|
|
8
|
+
Dropdown,
|
|
9
|
+
Form,
|
|
10
|
+
InlineNotification,
|
|
11
|
+
RadioButton,
|
|
12
|
+
RadioButtonGroup,
|
|
13
|
+
Row,
|
|
14
|
+
Stack,
|
|
15
|
+
TextInput,
|
|
16
|
+
} from '@carbon/react';
|
|
3
17
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
18
|
+
import { showSnackbar, useAppContext, useConfig } from '@openmrs/esm-framework';
|
|
19
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
4
20
|
import { Controller, useForm } from 'react-hook-form';
|
|
5
21
|
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import {
|
|
7
|
-
import { showSnackbar, useAppContext } from '@openmrs/esm-framework';
|
|
8
|
-
import type { WardPatientWorkspaceProps, WardViewContext } from '../../types';
|
|
22
|
+
import { type WardConfigObject } from '../../config-schema';
|
|
9
23
|
import { useAssignedBedByPatient } from '../../hooks/useAssignedBedByPatient';
|
|
10
|
-
import { assignPatientToBed, removePatientFromBed, useAdmitPatient } from '../../ward.resource';
|
|
11
24
|
import useWardLocation from '../../hooks/useWardLocation';
|
|
25
|
+
import type { WardPatientWorkspaceProps, WardViewContext } from '../../types';
|
|
26
|
+
import { assignPatientToBed, removePatientFromBed, useAdmitPatient } from '../../ward.resource';
|
|
12
27
|
import BedSelector from '../bed-selector.component';
|
|
13
28
|
import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
|
|
14
29
|
import styles from './admit-patient-form.scss';
|
|
30
|
+
import DiagnosisInput from './diagnosis-input.component';
|
|
31
|
+
import {
|
|
32
|
+
formValuesToObs,
|
|
33
|
+
type InapatientAdmissionFormData,
|
|
34
|
+
inpatientAdmissionSchema,
|
|
35
|
+
useProviders,
|
|
36
|
+
} from './patient-admission.resources';
|
|
15
37
|
|
|
16
38
|
/**
|
|
17
39
|
* This form gets rendered when the user clicks "admit patient" in
|
|
@@ -27,7 +49,7 @@ const AdmitPatientFormWorkspace: React.FC<WardPatientWorkspaceProps> = ({
|
|
|
27
49
|
}) => {
|
|
28
50
|
const { patient, inpatientRequest, visit } = wardPatient ?? {};
|
|
29
51
|
const dispositionType = inpatientRequest?.dispositionType ?? 'ADMIT';
|
|
30
|
-
|
|
52
|
+
const config = useConfig<WardConfigObject>();
|
|
31
53
|
const { t } = useTranslation();
|
|
32
54
|
const { location } = useWardLocation();
|
|
33
55
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
@@ -41,33 +63,32 @@ const AdmitPatientFormWorkspace: React.FC<WardPatientWorkspaceProps> = ({
|
|
|
41
63
|
);
|
|
42
64
|
const beds = isLoading ? [] : wardPatientGroupDetails?.bedLayouts ?? [];
|
|
43
65
|
|
|
44
|
-
const zodSchema = useMemo(
|
|
45
|
-
() =>
|
|
46
|
-
z.object({
|
|
47
|
-
bedId: z.number().optional(),
|
|
48
|
-
}),
|
|
49
|
-
[],
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
type FormValues = z.infer<typeof zodSchema>;
|
|
53
|
-
|
|
54
66
|
const {
|
|
55
67
|
control,
|
|
56
68
|
formState: { errors, isDirty },
|
|
57
69
|
handleSubmit,
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
watch,
|
|
71
|
+
} = useForm<InapatientAdmissionFormData>({
|
|
72
|
+
defaultValues: { admissionDate: new Date() },
|
|
73
|
+
resolver: zodResolver(inpatientAdmissionSchema),
|
|
60
74
|
});
|
|
75
|
+
const { isLoading: isLoadingProviders, providers } = useProviders();
|
|
76
|
+
const [paymentMethodObservable, insuaranceTypeObservable, bedIdObservable] = watch([
|
|
77
|
+
'paymentMode',
|
|
78
|
+
'insuranceType',
|
|
79
|
+
'bedId',
|
|
80
|
+
]);
|
|
61
81
|
|
|
62
82
|
useEffect(() => {
|
|
63
83
|
promptBeforeClosing(() => isDirty);
|
|
64
84
|
}, [isDirty, promptBeforeClosing]);
|
|
65
85
|
|
|
66
|
-
const onSubmit = (values:
|
|
86
|
+
const onSubmit = (values: InapatientAdmissionFormData) => {
|
|
67
87
|
setShowErrorNotifications(false);
|
|
68
88
|
setIsSubmitting(true);
|
|
69
89
|
const bedSelected = beds.find((bed) => bed.bedId === values.bedId);
|
|
70
|
-
|
|
90
|
+
const obs = formValuesToObs(values, config);
|
|
91
|
+
admitPatient(patient, dispositionType, visit.uuid, obs)
|
|
71
92
|
.then(
|
|
72
93
|
async (response) => {
|
|
73
94
|
if (response.ok) {
|
|
@@ -143,34 +164,232 @@ const AdmitPatientFormWorkspace: React.FC<WardPatientWorkspaceProps> = ({
|
|
|
143
164
|
if (!wardPatientGroupDetails) return <></>;
|
|
144
165
|
|
|
145
166
|
return (
|
|
146
|
-
<
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
beds={beds}
|
|
161
|
-
isLoadingBeds={isLoading}
|
|
162
|
-
currentPatient={patient}
|
|
163
|
-
selectedBedId={value}
|
|
164
|
-
error={error}
|
|
165
|
-
control={control}
|
|
166
|
-
onChange={onChange}
|
|
167
|
-
/>
|
|
168
|
-
);
|
|
167
|
+
<Form control={control} className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
|
|
168
|
+
<Stack gap={4} className={styles.grid}>
|
|
169
|
+
<WardPatientWorkspaceBanner {...{ wardPatient }} />
|
|
170
|
+
<Column>
|
|
171
|
+
<Controller
|
|
172
|
+
control={control}
|
|
173
|
+
name="admissionDate"
|
|
174
|
+
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
|
175
|
+
return (
|
|
176
|
+
<DatePicker
|
|
177
|
+
value={value}
|
|
178
|
+
datePickerType="single"
|
|
179
|
+
onChange={([date]) => {
|
|
180
|
+
onChange(date);
|
|
169
181
|
}}
|
|
182
|
+
className={styles.datePickerInput}>
|
|
183
|
+
<DatePickerInput
|
|
184
|
+
id="admission-date"
|
|
185
|
+
labelText={t('admissionDate', 'Admission Date')}
|
|
186
|
+
placeholder="mm/dd/yyyy"
|
|
187
|
+
invalid={error?.message}
|
|
188
|
+
invalidText={error?.message}
|
|
189
|
+
/>
|
|
190
|
+
</DatePicker>
|
|
191
|
+
);
|
|
192
|
+
}}
|
|
193
|
+
/>
|
|
194
|
+
</Column>
|
|
195
|
+
<Column>
|
|
196
|
+
<DiagnosisInput control={control} name={'diagnosis'} />
|
|
197
|
+
</Column>
|
|
198
|
+
<Column>
|
|
199
|
+
<Controller
|
|
200
|
+
control={control}
|
|
201
|
+
name="primaryDoctor"
|
|
202
|
+
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
|
203
|
+
return (
|
|
204
|
+
<ComboBox
|
|
205
|
+
id="primary-doctor"
|
|
206
|
+
invalid={error?.message}
|
|
207
|
+
helperText={isLoadingProviders ? 'Loading....' : undefined}
|
|
208
|
+
invalidText={error?.message}
|
|
209
|
+
itemToString={(provider) => providers.find((p) => p.uuid === provider)?.display ?? ''}
|
|
210
|
+
items={providers.map((p) => p.uuid)}
|
|
211
|
+
selectedItem={value}
|
|
212
|
+
onChange={({ selectedItem }) => onChange(selectedItem)}
|
|
213
|
+
placeholder={t('primaryDoctor', 'Primary Doctor')}
|
|
214
|
+
titleText={t('primaryDoctor', 'Primary Doctor')}
|
|
215
|
+
type="default"
|
|
170
216
|
/>
|
|
171
|
-
|
|
217
|
+
);
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
</Column>
|
|
221
|
+
<Column>
|
|
222
|
+
<Controller
|
|
223
|
+
control={control}
|
|
224
|
+
name="primaryDoctorPhoneNumber"
|
|
225
|
+
render={({ field, fieldState: { error } }) => {
|
|
226
|
+
return (
|
|
227
|
+
<TextInput
|
|
228
|
+
{...field}
|
|
229
|
+
invalid={error?.message}
|
|
230
|
+
invalidText={error?.message}
|
|
231
|
+
id="primary-doctor-phone-number"
|
|
232
|
+
labelText={t('primaryDoctorPhoneNumber', 'Primary doctor phone number')}
|
|
233
|
+
placeholder={t('phoneNumber', 'Phone number')}
|
|
234
|
+
size="md"
|
|
235
|
+
type="text"
|
|
236
|
+
/>
|
|
237
|
+
);
|
|
238
|
+
}}
|
|
239
|
+
/>
|
|
240
|
+
</Column>
|
|
241
|
+
<Column>
|
|
242
|
+
<Controller
|
|
243
|
+
control={control}
|
|
244
|
+
name="emergencyDoctor"
|
|
245
|
+
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
|
246
|
+
return (
|
|
247
|
+
<ComboBox
|
|
248
|
+
id="emergency-doctor"
|
|
249
|
+
invalid={error?.message}
|
|
250
|
+
helperText={isLoadingProviders ? 'Loading....' : undefined}
|
|
251
|
+
invalidText={error?.message}
|
|
252
|
+
itemToString={(provider) => providers.find((p) => p.uuid === provider)?.display ?? ''}
|
|
253
|
+
items={providers.map((p) => p.uuid)}
|
|
254
|
+
selectedItem={value}
|
|
255
|
+
onChange={({ selectedItem }) => onChange(selectedItem)}
|
|
256
|
+
placeholder={t('emergencyDoctor', 'Emergence Doctor')}
|
|
257
|
+
titleText={t('emergencyDoctor', 'Emergency Doctor')}
|
|
258
|
+
type="default"
|
|
259
|
+
/>
|
|
260
|
+
);
|
|
261
|
+
}}
|
|
262
|
+
/>
|
|
263
|
+
</Column>
|
|
264
|
+
<Column>
|
|
265
|
+
<Controller
|
|
266
|
+
control={control}
|
|
267
|
+
name="emergencyDoctorPhoneNumber"
|
|
268
|
+
render={({ field, fieldState: { error } }) => {
|
|
269
|
+
return (
|
|
270
|
+
<TextInput
|
|
271
|
+
{...field}
|
|
272
|
+
invalid={error?.message}
|
|
273
|
+
invalidText={error?.message}
|
|
274
|
+
id="emergency-doctor-phone-number"
|
|
275
|
+
labelText={t('emergencyDoctorPhoneNumber', 'Emergency doctor phone number')}
|
|
276
|
+
placeholder={t('phoneNumber', 'Phone number')}
|
|
277
|
+
size="md"
|
|
278
|
+
type="text"
|
|
279
|
+
/>
|
|
280
|
+
);
|
|
281
|
+
}}
|
|
282
|
+
/>
|
|
283
|
+
</Column>
|
|
284
|
+
<Column>
|
|
285
|
+
<Controller
|
|
286
|
+
control={control}
|
|
287
|
+
name="paymentMode"
|
|
288
|
+
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
|
289
|
+
return (
|
|
290
|
+
<RadioButtonGroup
|
|
291
|
+
legendText={t('paymentMode', 'Payment mode')}
|
|
292
|
+
name="payment-mode"
|
|
293
|
+
defaultSelected={value}
|
|
294
|
+
invalid={error?.message}
|
|
295
|
+
invalidText={error?.message}
|
|
296
|
+
onChange={onChange}
|
|
297
|
+
orientation="vertical">
|
|
298
|
+
<RadioButton
|
|
299
|
+
labelText={t('cash', 'Cash')}
|
|
300
|
+
value={config.conceptUuidForWardAdmission.cashPaymentMethod}
|
|
301
|
+
/>
|
|
302
|
+
<RadioButton
|
|
303
|
+
labelText={t('mpesa', 'MPESA')}
|
|
304
|
+
value={config.conceptUuidForWardAdmission.mpesaPaymentMethod}
|
|
305
|
+
/>
|
|
306
|
+
<RadioButton
|
|
307
|
+
labelText={t('insuarance', 'Insuarance')}
|
|
308
|
+
value={config.conceptUuidForWardAdmission.insurancePaymentMethod}
|
|
309
|
+
/>
|
|
310
|
+
</RadioButtonGroup>
|
|
311
|
+
);
|
|
312
|
+
}}
|
|
313
|
+
/>
|
|
314
|
+
</Column>
|
|
315
|
+
{paymentMethodObservable === config.conceptUuidForWardAdmission.insurancePaymentMethod && (
|
|
316
|
+
<Column>
|
|
317
|
+
<Controller
|
|
318
|
+
control={control}
|
|
319
|
+
name="insuranceType"
|
|
320
|
+
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
|
321
|
+
return (
|
|
322
|
+
<Dropdown
|
|
323
|
+
invalid={error?.message}
|
|
324
|
+
invalidText={error?.message}
|
|
325
|
+
selectedItem={value}
|
|
326
|
+
onChange={({ selectedItem }) => onChange(selectedItem)}
|
|
327
|
+
id="insurance-type"
|
|
328
|
+
itemToString={(concept) =>
|
|
329
|
+
config.insuaranceTypes.find((type) => type.concept === concept)?.label ?? ''
|
|
330
|
+
}
|
|
331
|
+
items={config.insuaranceTypes.map((type) => type.concept)}
|
|
332
|
+
label={t('insuranceType', 'Insurance type')}
|
|
333
|
+
titleText={t('insuranceType', 'Insurance type')}
|
|
334
|
+
type="default"
|
|
335
|
+
/>
|
|
336
|
+
);
|
|
337
|
+
}}
|
|
338
|
+
/>
|
|
339
|
+
</Column>
|
|
340
|
+
)}
|
|
341
|
+
{paymentMethodObservable === config.conceptUuidForWardAdmission.insurancePaymentMethod &&
|
|
342
|
+
insuaranceTypeObservable === config.conceptUuidForWardAdmission.otherInsuaranceType && (
|
|
343
|
+
<Column>
|
|
344
|
+
<Controller
|
|
345
|
+
control={control}
|
|
346
|
+
name="otherInsuranceType"
|
|
347
|
+
render={({ field, fieldState: { error } }) => {
|
|
348
|
+
return (
|
|
349
|
+
<TextInput
|
|
350
|
+
{...field}
|
|
351
|
+
invalid={error?.message}
|
|
352
|
+
invalidText={error?.message}
|
|
353
|
+
id="other-insurance-type"
|
|
354
|
+
labelText={t('otherInsuranceType', 'Other Insurance Type')}
|
|
355
|
+
placeholder={t('pleaseSpecify', 'Please specify')}
|
|
356
|
+
size="md"
|
|
357
|
+
type="text"
|
|
358
|
+
/>
|
|
359
|
+
);
|
|
360
|
+
}}
|
|
361
|
+
/>
|
|
172
362
|
</Column>
|
|
173
|
-
|
|
363
|
+
)}
|
|
364
|
+
<Column>
|
|
365
|
+
<Controller
|
|
366
|
+
control={control}
|
|
367
|
+
name="bedId"
|
|
368
|
+
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
|
369
|
+
return (
|
|
370
|
+
<BedSelector
|
|
371
|
+
beds={beds}
|
|
372
|
+
isLoadingBeds={isLoading}
|
|
373
|
+
currentPatient={patient}
|
|
374
|
+
selectedBedId={value}
|
|
375
|
+
error={error}
|
|
376
|
+
control={control}
|
|
377
|
+
onChange={onChange}
|
|
378
|
+
/>
|
|
379
|
+
);
|
|
380
|
+
}}
|
|
381
|
+
/>
|
|
382
|
+
</Column>
|
|
383
|
+
<Column>
|
|
384
|
+
<TextInput
|
|
385
|
+
value={beds?.find((b) => b.bedId === bedIdObservable)?.bedType?.displayName ?? ''}
|
|
386
|
+
readOnly
|
|
387
|
+
labelText={t('bedType', 'Bed type')}
|
|
388
|
+
placeholder={t('notConfigured', 'Bed type not configured')}
|
|
389
|
+
/>
|
|
390
|
+
</Column>
|
|
391
|
+
|
|
392
|
+
<Column>
|
|
174
393
|
<div className={styles.errorNotifications}>
|
|
175
394
|
{showErrorNotifications &&
|
|
176
395
|
Object.entries(errors).map(([key, value]) => {
|
|
@@ -183,26 +402,32 @@ const AdmitPatientFormWorkspace: React.FC<WardPatientWorkspaceProps> = ({
|
|
|
183
402
|
);
|
|
184
403
|
})}
|
|
185
404
|
</div>
|
|
186
|
-
</
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
405
|
+
</Column>
|
|
406
|
+
</Stack>
|
|
407
|
+
|
|
408
|
+
<ButtonSet className={styles.buttonSet}>
|
|
409
|
+
<Button
|
|
410
|
+
className={styles.button}
|
|
411
|
+
size="xl"
|
|
412
|
+
kind="secondary"
|
|
413
|
+
onClick={() => closeWorkspace({ ignoreChanges: true })}>
|
|
414
|
+
{t('cancel', 'Cancel')}
|
|
415
|
+
</Button>
|
|
416
|
+
<Button
|
|
417
|
+
className={styles.button}
|
|
418
|
+
type="submit"
|
|
419
|
+
size="xl"
|
|
420
|
+
disabled={
|
|
421
|
+
isSubmitting ||
|
|
422
|
+
isLoadingEmrConfiguration ||
|
|
423
|
+
errorFetchingEmrConfiguration ||
|
|
424
|
+
isLoading ||
|
|
425
|
+
isLoadingBedsAssignedToPatient
|
|
426
|
+
}>
|
|
427
|
+
{!isSubmitting ? t('admit', 'Admit') : t('admitting', 'Admitting...')}
|
|
428
|
+
</Button>
|
|
429
|
+
</ButtonSet>
|
|
430
|
+
</Form>
|
|
206
431
|
);
|
|
207
432
|
};
|
|
208
433
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ComboBox } from '@carbon/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { type Control, Controller, type Path } from 'react-hook-form';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { useDiagnoses } from './patient-admission.resources';
|
|
6
|
+
|
|
7
|
+
type Props<T> = {
|
|
8
|
+
control: Control<T>;
|
|
9
|
+
name: Path<T>;
|
|
10
|
+
};
|
|
11
|
+
const DiagnosisInput = <T,>({ control, name }: Props<T>) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const { diagnoses, onSearchTermChange, isLoading } = useDiagnoses();
|
|
14
|
+
return (
|
|
15
|
+
<Controller
|
|
16
|
+
control={control}
|
|
17
|
+
name={name}
|
|
18
|
+
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
|
19
|
+
return (
|
|
20
|
+
<ComboBox
|
|
21
|
+
id="diagnosis"
|
|
22
|
+
invalid={error?.message}
|
|
23
|
+
invalidText={error?.message}
|
|
24
|
+
itemToString={(concept) => diagnoses.find((c) => c.uuid === concept)?.display ?? ''}
|
|
25
|
+
items={diagnoses.map((d) => d.uuid)}
|
|
26
|
+
placeholder={t('diagnosis', 'Diagnosis')}
|
|
27
|
+
titleText={t('majorComplaintOrDiagnosis', 'Major Complaint/Diagnosis')}
|
|
28
|
+
onInputChange={onSearchTermChange}
|
|
29
|
+
type="default"
|
|
30
|
+
selectedItem={value}
|
|
31
|
+
helperText={isLoading ? 'Loading....' : undefined}
|
|
32
|
+
onChange={({ selectedItem }) => {
|
|
33
|
+
onChange(selectedItem);
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default DiagnosisInput;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type Concept, restBaseUrl, useConfig, useDebounce, useOpenmrsFetchAll } from '@openmrs/esm-framework';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
import { type WardConfigObject } from '../../config-schema';
|
|
5
|
+
import dayjs from 'dayjs';
|
|
6
|
+
export const useDiagnoses = () => {
|
|
7
|
+
const { diagnosisConceptSourceUud } = useConfig<WardConfigObject>();
|
|
8
|
+
const [q, setQ] = useState('');
|
|
9
|
+
const debouncedSearchTerm = useDebounce(q, 500);
|
|
10
|
+
const rep =
|
|
11
|
+
'custom:(uuid,name:(uuid,display),conceptClass:(uuid,display),setMembers,mappings:(conceptReferenceTerm:(code,name,display,conceptSource:(uuid))))';
|
|
12
|
+
const url = `${restBaseUrl}/concept?q=${debouncedSearchTerm}&v=${rep}&references=${diagnosisConceptSourceUud}`;
|
|
13
|
+
const { data, error, isLoading } = useOpenmrsFetchAll<Concept>(debouncedSearchTerm?.length >= 3 ? url : null);
|
|
14
|
+
const diagnoses = useMemo(
|
|
15
|
+
() =>
|
|
16
|
+
(data ?? [])
|
|
17
|
+
.filter(
|
|
18
|
+
(c) => c.mappings?.some((m) => m?.conceptReferenceTerm?.conceptSource?.uuid === diagnosisConceptSourceUud),
|
|
19
|
+
)
|
|
20
|
+
.map((c) => {
|
|
21
|
+
const code = c.mappings?.find(
|
|
22
|
+
(m) => m?.conceptReferenceTerm?.conceptSource?.uuid === diagnosisConceptSourceUud,
|
|
23
|
+
)?.conceptReferenceTerm?.code;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
display: `${code}-${c.name.display}`,
|
|
27
|
+
uuid: c.uuid,
|
|
28
|
+
};
|
|
29
|
+
}),
|
|
30
|
+
[data, diagnosisConceptSourceUud],
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
diagnoses,
|
|
34
|
+
isLoading,
|
|
35
|
+
error,
|
|
36
|
+
searchTerm: q,
|
|
37
|
+
onSearchTermChange: setQ,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type Provider = {
|
|
42
|
+
uuid: string;
|
|
43
|
+
display: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const useProviders = () => {
|
|
47
|
+
const url = `${restBaseUrl}/provider?v=custom:(uuid,display)`;
|
|
48
|
+
const { data, error, isLoading } = useOpenmrsFetchAll<Provider>(url);
|
|
49
|
+
return {
|
|
50
|
+
providers: data ?? [],
|
|
51
|
+
error,
|
|
52
|
+
isLoading,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const inpatientAdmissionSchema = z.object({
|
|
57
|
+
bedId: z.number().optional(),
|
|
58
|
+
admissionDate: z.date({ coerce: true }),
|
|
59
|
+
diagnosis: z.string().optional(),
|
|
60
|
+
primaryDoctor: z.string().optional(),
|
|
61
|
+
primaryDoctorPhoneNumber: z.string().optional(),
|
|
62
|
+
emergencyDoctor: z.string().optional(),
|
|
63
|
+
emergencyDoctorPhoneNumber: z.string().optional(),
|
|
64
|
+
paymentMode: z.string().optional(),
|
|
65
|
+
insuranceType: z.string().optional(),
|
|
66
|
+
otherInsuranceType: z.string().optional(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const formartDate = (date: Date) => {
|
|
70
|
+
return date ? dayjs(date).format('YYYY-MM-DD HH:mm:ss') : '';
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type InapatientAdmissionFormData = z.infer<typeof inpatientAdmissionSchema>;
|
|
74
|
+
export const formValuesToObs = (data: InapatientAdmissionFormData, config: WardConfigObject) => {
|
|
75
|
+
const obs = [
|
|
76
|
+
{
|
|
77
|
+
concept: config.conceptUuidForWardAdmission.admissionDateTime,
|
|
78
|
+
value: formartDate(data?.admissionDate),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
concept: config.conceptUuidForWardAdmission.primaryDoctor,
|
|
82
|
+
value: data.primaryDoctor,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
concept: config.conceptUuidForWardAdmission.chiefComplaint,
|
|
86
|
+
value: data.diagnosis,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
concept: config.conceptUuidForWardAdmission.primaryDoctorPhoneNumber,
|
|
90
|
+
value: data.primaryDoctorPhoneNumber,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
concept: config.conceptUuidForWardAdmission.emmergencyDoctor,
|
|
94
|
+
value: data.emergencyDoctor,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
concept: config.conceptUuidForWardAdmission.emmergencyDoctorPhoneNumber,
|
|
98
|
+
value: data.emergencyDoctorPhoneNumber,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
concept: config.conceptUuidForWardAdmission.paymentMethod,
|
|
102
|
+
value: data.paymentMode,
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
if (data.paymentMode === config.conceptUuidForWardAdmission.insurancePaymentMethod) {
|
|
106
|
+
obs.push({
|
|
107
|
+
concept: config.conceptUuidForWardAdmission.insurancePaymentMethod,
|
|
108
|
+
value: data.insuranceType,
|
|
109
|
+
});
|
|
110
|
+
if (data.otherInsuranceType === config.conceptUuidForWardAdmission.otherInsuaranceType) {
|
|
111
|
+
obs.push({
|
|
112
|
+
concept: config.conceptUuidForWardAdmission.insuranceOtherSpecify,
|
|
113
|
+
value: data.otherInsuranceType,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return obs.filter((o) => Boolean(o.value));
|
|
118
|
+
};
|
|
@@ -22,6 +22,7 @@ interface BedDropdownItem {
|
|
|
22
22
|
bedId: number;
|
|
23
23
|
label: string;
|
|
24
24
|
disabled: boolean;
|
|
25
|
+
bedType: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const BedSelector: React.FC<BedSelectorProps> = ({
|
|
@@ -43,14 +44,19 @@ const BedSelector: React.FC<BedSelectorProps> = ({
|
|
|
43
44
|
bedLayout.patients.length === 0
|
|
44
45
|
? [t('emptyText', 'Empty')]
|
|
45
46
|
: bedLayout.patients.map((patient) => patient?.person?.preferredName?.display);
|
|
46
|
-
return [bedNumber, ...patients].join(' · ');
|
|
47
|
+
return [bedNumber, bedLayout?.bedType?.displayName ?? 'Type unconfigured', ...patients].join(' · ');
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
const bedDropdownItems: BedDropdownItem[] = [
|
|
50
|
-
{ bedId: 0, label: t('noBed', 'No bed'), disabled: false },
|
|
51
|
+
{ bedId: 0, label: t('noBed', 'No bed'), bedType: '', disabled: false },
|
|
51
52
|
...beds.map((bed) => {
|
|
52
53
|
const isPatientAssignedToBed = bed.patients.some((bedPatient) => bedPatient.uuid === currentPatient.uuid);
|
|
53
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
bedId: bed.bedId,
|
|
56
|
+
label: getBedRepresentation(bed),
|
|
57
|
+
bedType: bed?.bedType?.displayName ?? '',
|
|
58
|
+
disabled: isPatientAssignedToBed,
|
|
59
|
+
};
|
|
54
60
|
}),
|
|
55
61
|
];
|
|
56
62
|
const selectedItem = bedDropdownItems.find((bed) => bed.bedId === selectedBedId);
|
|
@@ -82,7 +88,7 @@ const BedSelector: React.FC<BedSelectorProps> = ({
|
|
|
82
88
|
return (
|
|
83
89
|
<Dropdown
|
|
84
90
|
id="default"
|
|
85
|
-
titleText=
|
|
91
|
+
titleText={t('selectABed', 'Select a bed')}
|
|
86
92
|
helperText=""
|
|
87
93
|
label={!selectedItem && t('chooseAnOption', 'Choose an option')}
|
|
88
94
|
items={bedDropdownItems}
|
|
@@ -101,7 +107,7 @@ const BedSelector: React.FC<BedSelectorProps> = ({
|
|
|
101
107
|
onChange={onChange}
|
|
102
108
|
invalid={!!error}
|
|
103
109
|
invalidText={error?.message}>
|
|
104
|
-
{bedDropdownItems.map(({ bedId, label, disabled }) => (
|
|
110
|
+
{bedDropdownItems.map(({ bedId, label, bedType, disabled }) => (
|
|
105
111
|
<RadioButton
|
|
106
112
|
key={bedId}
|
|
107
113
|
labelText={label}
|
package/src/ward.resource.ts
CHANGED
|
@@ -44,14 +44,14 @@ export function useAdmitPatient() {
|
|
|
44
44
|
const { createEncounter, emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } =
|
|
45
45
|
useCreateEncounter();
|
|
46
46
|
|
|
47
|
-
const admitPatient = (patient: Patient, dispositionType: DispositionType, visitUuid: string) => {
|
|
47
|
+
const admitPatient = (patient: Patient, dispositionType: DispositionType, visitUuid: string, obs?: ObsPayload[]) => {
|
|
48
48
|
const encounterType =
|
|
49
49
|
dispositionType === 'ADMIT'
|
|
50
50
|
? emrConfiguration.admissionEncounterType
|
|
51
51
|
: dispositionType === 'TRANSFER'
|
|
52
52
|
? emrConfiguration.transferWithinHospitalEncounterType
|
|
53
53
|
: null;
|
|
54
|
-
return createEncounter(patient, encounterType, visitUuid);
|
|
54
|
+
return createEncounter(patient, encounterType, visitUuid, obs);
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
return { admitPatient, isLoadingEmrConfiguration, errorFetchingEmrConfiguration };
|