@kenyaemr/esm-ward-app 8.5.1-pre.37 → 8.5.1-pre.44
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 +12 -12
- package/dist/1160.js +1 -0
- package/dist/1160.js.map +1 -0
- package/dist/1917.js +1 -1
- package/dist/1917.js.map +1 -1
- package/dist/4224.js +2 -0
- package/dist/4224.js.map +1 -0
- package/dist/6009.js +1 -0
- package/dist/6009.js.map +1 -0
- package/dist/6012.js +1 -1
- package/dist/6012.js.map +1 -1
- package/dist/6871.js +1 -1
- package/dist/6871.js.map +1 -1
- package/dist/7059.js +2 -0
- package/dist/7059.js.map +1 -0
- package/dist/7661.js +1 -1
- package/dist/7661.js.map +1 -1
- package/dist/8205.js +1 -1
- package/dist/8205.js.map +1 -1
- package/dist/8308.js +1 -0
- package/dist/8308.js.map +1 -0
- package/dist/9113.js +1 -0
- package/dist/{9880.js.map → 9113.js.map} +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +162 -111
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -1
- package/src/config-schema.ts +28 -0
- package/src/discharge-printouts/discharge-printout.preview-modal.tsx +40 -0
- package/src/discharge-printouts/discharge-printout.resource.ts +335 -0
- package/src/discharge-printouts/discharge-printouts.scss +125 -0
- package/src/discharge-printouts/discharge-summary.tsx +170 -0
- package/src/discharge-printouts/field-input.tsx +21 -0
- package/src/discharge-printouts/gate-pass-printout.tsx +125 -0
- package/src/discharge-printouts/lab-results.tsx +96 -0
- package/src/hooks/useIpdDischargeEncounter.ts +10 -8
- package/src/index.ts +4 -0
- package/src/routes.json +4 -0
- package/src/ward-patients/admitted-patients.tsx +2 -2
- package/src/ward-patients/discharge-in-patients.tsx +22 -18
- package/src/ward-patients/discharge-patients.tsx +23 -3
- package/src/ward-patients/patient-cells.tsx +4 -10
- package/src/ward-workspace/admit-patient-form-workspace/patient-admission.resources.ts +19 -1
- package/src/ward-workspace/kenya-emr-patient-discharge/patient-discharge.resource.tsx +2 -1
- package/src/ward-workspace/patient-transfer-bed-swap/patient-admit-or-transfer-request-form.component.tsx +1 -1
- package/dist/1352.js +0 -2
- package/dist/1352.js.map +0 -1
- package/dist/3423.js +0 -1
- package/dist/3423.js.map +0 -1
- package/dist/4701.js +0 -2
- package/dist/4701.js.map +0 -1
- package/dist/9880.js +0 -1
- /package/dist/{1352.js.LICENSE.txt → 4224.js.LICENSE.txt} +0 -0
- /package/dist/{4701.js.LICENSE.txt → 7059.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { InlineLoading, InlineNotification } from '@carbon/react';
|
|
2
|
+
import { useEmrConfiguration, usePatient, useSession } from '@openmrs/esm-framework';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import React, { type FC, useMemo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { useEncounterDetails } from '../hooks/useIpdDischargeEncounter';
|
|
7
|
+
import { useProvider } from '../ward-workspace/admit-patient-form-workspace/patient-admission.resources';
|
|
8
|
+
import {
|
|
9
|
+
DATE_FORMART,
|
|
10
|
+
getTreatmentDisplayText,
|
|
11
|
+
usePatientDiagnosis,
|
|
12
|
+
usePatientOrders,
|
|
13
|
+
} from './discharge-printout.resource';
|
|
14
|
+
import styles from './discharge-printouts.scss';
|
|
15
|
+
import FieldInput from './field-input';
|
|
16
|
+
import LabResults from './lab-results';
|
|
17
|
+
|
|
18
|
+
type DischargeSummaryProps = {
|
|
19
|
+
dischargeEncounterUuid: string;
|
|
20
|
+
patient: {
|
|
21
|
+
uuid: string;
|
|
22
|
+
openmrsId: string;
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const DischargeSummary: FC<DischargeSummaryProps> = ({ dischargeEncounterUuid, patient: _patient }) => {
|
|
28
|
+
const { t } = useTranslation();
|
|
29
|
+
const { encounter, error, isLoading } = useEncounterDetails(dischargeEncounterUuid);
|
|
30
|
+
const { isLoading: isLoadingPatient, patient, error: patientError } = usePatient(_patient.uuid);
|
|
31
|
+
const {
|
|
32
|
+
isLoading: isLoadingDiagnosis,
|
|
33
|
+
error: diagnosisError,
|
|
34
|
+
display: diagnoses,
|
|
35
|
+
} = usePatientDiagnosis(dischargeEncounterUuid);
|
|
36
|
+
const session = useSession();
|
|
37
|
+
const { error: errorProvider, isLoading: isLoadingProvider, provider } = useProvider(session.currentProvider.uuid);
|
|
38
|
+
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
39
|
+
const {
|
|
40
|
+
isLoading: isLoadingOders,
|
|
41
|
+
error: orderserror,
|
|
42
|
+
drugOrders,
|
|
43
|
+
testOrders,
|
|
44
|
+
complaints,
|
|
45
|
+
drugReactions,
|
|
46
|
+
orderEncounters,
|
|
47
|
+
dischargeinstructions,
|
|
48
|
+
physicalExaminations,
|
|
49
|
+
} = usePatientOrders(dischargeEncounterUuid);
|
|
50
|
+
const admissionDate = useMemo(() => {
|
|
51
|
+
const admisionEncounter = encounter?.visit?.encounters?.find(
|
|
52
|
+
(e) => e.encounterType.uuid === emrConfiguration?.admissionEncounterType?.uuid,
|
|
53
|
+
);
|
|
54
|
+
if (!admisionEncounter || !admisionEncounter.encounterDatetime) return null;
|
|
55
|
+
return admisionEncounter.encounterDatetime;
|
|
56
|
+
}, [encounter, emrConfiguration]);
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
isLoading ||
|
|
60
|
+
isLoadingPatient ||
|
|
61
|
+
isLoadingEmrConfiguration ||
|
|
62
|
+
isLoadingDiagnosis ||
|
|
63
|
+
isLoadingProvider ||
|
|
64
|
+
isLoadingOders
|
|
65
|
+
)
|
|
66
|
+
return <InlineLoading />;
|
|
67
|
+
if (error || patientError || errorFetchingEmrConfiguration || diagnosisError || errorProvider || orderserror)
|
|
68
|
+
return (
|
|
69
|
+
<InlineNotification
|
|
70
|
+
kind="error"
|
|
71
|
+
title={
|
|
72
|
+
error?.message ??
|
|
73
|
+
patientError?.message ??
|
|
74
|
+
errorFetchingEmrConfiguration?.message ??
|
|
75
|
+
diagnosisError?.message ??
|
|
76
|
+
errorProvider?.message ??
|
|
77
|
+
orderserror?.message
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
return (
|
|
82
|
+
<div className={styles.content}>
|
|
83
|
+
<div className={styles.header}>
|
|
84
|
+
<h5>{session.sessionLocation.display}</h5>
|
|
85
|
+
<h5>{t('dischargeSummary', 'Discharge Summary')}</h5>
|
|
86
|
+
</div>
|
|
87
|
+
<div className={styles.cols3}>
|
|
88
|
+
<FieldInput name={t('name', 'Name')} value={_patient.name} />
|
|
89
|
+
<FieldInput name={t('ipNo', 'IP No')} value={_patient.openmrsId} />
|
|
90
|
+
<FieldInput
|
|
91
|
+
name={t('age', 'Age')}
|
|
92
|
+
value={`${Math.abs(dayjs(patient.birthDate).diff(dayjs(), 'years'))} years`}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
<div className={styles.cols3}>
|
|
96
|
+
<FieldInput name={t('sex', 'Sex')} value={patient.gender} />
|
|
97
|
+
<FieldInput name={t('dateOfAdmissionAbrv', 'DOA')} value={dayjs(admissionDate).format(DATE_FORMART)} />
|
|
98
|
+
<FieldInput
|
|
99
|
+
name={t('dateOfDischargeAbrv', 'DOD')}
|
|
100
|
+
value={dayjs(encounter.encounterDatetime).format(DATE_FORMART)}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<div className={styles.cols2}>
|
|
104
|
+
<FieldInput name={t('nameOfConsultant', 'Name of consultant')} value={provider?.display?.split('-')?.at(-1)} />
|
|
105
|
+
<FieldInput name={t('department', 'Department')} value={encounter.location?.display} />
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div>
|
|
109
|
+
<strong className={styles.txtUpper}>{t('diagnosis', 'Diagnosis')}</strong>
|
|
110
|
+
<p className={styles.txtTitle}>{diagnoses ?? t('noDiagnoses', 'No Diagnoses')}</p>
|
|
111
|
+
</div>
|
|
112
|
+
<div>
|
|
113
|
+
<strong className={styles.txtUpper}>{t('history', 'History')}</strong>
|
|
114
|
+
<p>
|
|
115
|
+
{`${complaints ? 'Presented with ' + complaints + '.' : ''}${
|
|
116
|
+
drugReactions
|
|
117
|
+
? t('knownDrugAllergies', 'Known drug allergies') + ': ' + drugReactions
|
|
118
|
+
: t('noKnownDrugAllergies', 'No known drug allergies')
|
|
119
|
+
}`}
|
|
120
|
+
</p>
|
|
121
|
+
</div>
|
|
122
|
+
<div>
|
|
123
|
+
<strong className={styles.txtUpper}>{t('physicalExamination', 'Physical Examination')}</strong>
|
|
124
|
+
{physicalExaminations?.length ? (
|
|
125
|
+
physicalExaminations?.map((examination, i) => (
|
|
126
|
+
<p key={i} className={styles.txtTitle}>
|
|
127
|
+
{examination}
|
|
128
|
+
</p>
|
|
129
|
+
))
|
|
130
|
+
) : (
|
|
131
|
+
<p>{t('noExaminations', 'No Examinations')}</p>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
134
|
+
<div>
|
|
135
|
+
<strong className={styles.txtUpper}>{t('investigation', 'Investigation')}</strong>
|
|
136
|
+
<p>
|
|
137
|
+
{testOrders.map((order) => (
|
|
138
|
+
<LabResults
|
|
139
|
+
order={order}
|
|
140
|
+
key={order.uuid}
|
|
141
|
+
labEncounter={orderEncounters.find((e) => e.uuid === order.encounter.uuid)}
|
|
142
|
+
/>
|
|
143
|
+
))}
|
|
144
|
+
</p>
|
|
145
|
+
</div>
|
|
146
|
+
<div>
|
|
147
|
+
<strong className={styles.txtUpper}>{t('treatment', 'Treatment')}</strong>
|
|
148
|
+
<div>
|
|
149
|
+
{drugOrders.map((order) => (
|
|
150
|
+
<p key={order.uuid}>{getTreatmentDisplayText(order)}</p>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
<div>
|
|
155
|
+
<strong className={styles.txtUpper}>{t('dischargeInstructions', 'Discharge Instructions')}</strong>
|
|
156
|
+
<p>{dischargeinstructions ?? t('noInstructions', 'No instructions')}</p>
|
|
157
|
+
</div>
|
|
158
|
+
<div className={styles.cols2}>
|
|
159
|
+
<FieldInput name={t('name', 'Name')} value={provider?.display?.split('-')?.at(-1)} />
|
|
160
|
+
<FieldInput name={t('signature', 'Signature')} />
|
|
161
|
+
</div>
|
|
162
|
+
<div className={styles.cols2}>
|
|
163
|
+
<FieldInput name={t('destination', 'Destination')} value={t('discharge', 'Discharge')} />
|
|
164
|
+
<FieldInput name={t('date', 'Date')} value={dayjs().format(DATE_FORMART)} />
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export default DischargeSummary;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import styles from './discharge-printouts.scss';
|
|
3
|
+
type FieldInputProps = {
|
|
4
|
+
name: string;
|
|
5
|
+
value?: string;
|
|
6
|
+
delimiter?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const FieldInput: FC<FieldInputProps> = ({ name: fieldName, value, delimiter = ':' }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className={styles.field}>
|
|
12
|
+
<span className={styles.name}>
|
|
13
|
+
{fieldName}
|
|
14
|
+
{delimiter && `${delimiter} `}
|
|
15
|
+
</span>
|
|
16
|
+
<strong className={styles.value}>{value}</strong>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default FieldInput;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { InlineLoading, InlineNotification } from '@carbon/react';
|
|
2
|
+
import { useEmrConfiguration, usePatient, useSession } from '@openmrs/esm-framework';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import React, { useMemo, type FC } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { useEncounterDetails } from '../hooks/useIpdDischargeEncounter';
|
|
7
|
+
import styles from './discharge-printouts.scss';
|
|
8
|
+
import FieldInput from './field-input';
|
|
9
|
+
import { DATE_FORMART, TIME_FORMART } from './discharge-printout.resource';
|
|
10
|
+
|
|
11
|
+
type GatePassPrintoutProps = {
|
|
12
|
+
dischargeEncounterUuid: string;
|
|
13
|
+
patient: {
|
|
14
|
+
uuid: string;
|
|
15
|
+
openmrsId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const GatePassPrintout: FC<GatePassPrintoutProps> = ({ dischargeEncounterUuid, patient: _patient }) => {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
|
|
23
|
+
const { encounter, error, isLoading } = useEncounterDetails(dischargeEncounterUuid);
|
|
24
|
+
const { isLoading: isLoadingPatient, patient, error: patientError } = usePatient(_patient.uuid);
|
|
25
|
+
const session = useSession();
|
|
26
|
+
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
27
|
+
|
|
28
|
+
const admissionDate = useMemo(() => {
|
|
29
|
+
const admisionEncounter = encounter?.visit?.encounters?.find(
|
|
30
|
+
(e) => e.encounterType.uuid === emrConfiguration?.admissionEncounterType?.uuid,
|
|
31
|
+
);
|
|
32
|
+
if (!admisionEncounter || !admisionEncounter.encounterDatetime) return null;
|
|
33
|
+
return admisionEncounter.encounterDatetime;
|
|
34
|
+
}, [encounter, emrConfiguration]);
|
|
35
|
+
|
|
36
|
+
if (isLoading || isLoadingPatient || isLoadingEmrConfiguration) return <InlineLoading />;
|
|
37
|
+
if (error || patientError || errorFetchingEmrConfiguration)
|
|
38
|
+
return (
|
|
39
|
+
<InlineNotification
|
|
40
|
+
kind="error"
|
|
41
|
+
title={error?.message ?? patientError?.message ?? errorFetchingEmrConfiguration?.message}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className={styles.content}>
|
|
47
|
+
<div className={styles.header}>
|
|
48
|
+
<h5>{session?.sessionLocation?.display}</h5>
|
|
49
|
+
<h5>{t('wardGatePass', 'Ward gate pass')}</h5>
|
|
50
|
+
</div>
|
|
51
|
+
<div className={styles.cols4}>
|
|
52
|
+
<FieldInput name={t('paperNo', 'Paper No')} value={_patient.openmrsId} />
|
|
53
|
+
<FieldInput name={t('patientNo', 'Patient No')} value={_patient.openmrsId} />
|
|
54
|
+
<FieldInput name={t('date', 'Date')} value={dayjs().format(DATE_FORMART)} />
|
|
55
|
+
<FieldInput name={t('time', 'Time')} value={dayjs().format(TIME_FORMART)} />
|
|
56
|
+
</div>
|
|
57
|
+
<div className={styles.cols2}>
|
|
58
|
+
<FieldInput name={t('patientNames', 'Patient names')} value={_patient.name} />
|
|
59
|
+
<FieldInput
|
|
60
|
+
name={t('age', 'Age')}
|
|
61
|
+
value={`${Math.abs(dayjs(patient.birthDate).diff(dayjs(), 'years'))} years`}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
<div className={styles.cols2}>
|
|
65
|
+
<FieldInput name={t('dateOfAdmissionAbrv', 'DOA')} value={dayjs(admissionDate).format(DATE_FORMART)} />
|
|
66
|
+
<FieldInput
|
|
67
|
+
name={t('dateOfDischargeAbrv', 'DOD')}
|
|
68
|
+
value={dayjs(encounter.encounterDatetime).format(DATE_FORMART)}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div>
|
|
73
|
+
<p>{t('methodsOfPayment', 'Method of payment (tick as approximately)')}</p>
|
|
74
|
+
<br />
|
|
75
|
+
<div className={styles.cols7}>
|
|
76
|
+
<span>{t('cashCheckBox', 'Cash [ ]')}</span>
|
|
77
|
+
<span>{t('chequCheckBox', 'Cheque [ ]')}</span>
|
|
78
|
+
<span>{t('shaCheckBox', 'SHA [ ]')}</span>
|
|
79
|
+
<span>{t('scheme', 'Scheme [ ]')}</span>
|
|
80
|
+
<span>{t('mrm', 'M.R.M [ ]')}</span>
|
|
81
|
+
<FieldInput name={t('other', 'Other')} />
|
|
82
|
+
<FieldInput name="." delimiter="." />
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<p>
|
|
86
|
+
<strong>{t('approvedBy', 'Approved By')}</strong>
|
|
87
|
+
</p>
|
|
88
|
+
<div className={styles.cols4}>
|
|
89
|
+
<FieldInput name={t('accountOfficer', 'Account Ofiicer')} />
|
|
90
|
+
<FieldInput name="." delimiter="." />
|
|
91
|
+
<FieldInput name={t('sign', 'Sign')} />
|
|
92
|
+
<FieldInput name={t('date', 'Date')} />
|
|
93
|
+
</div>
|
|
94
|
+
<div className={styles.cols4}>
|
|
95
|
+
<FieldInput name={t('healthRecordOfficer', 'Health Record Officer')} />
|
|
96
|
+
<FieldInput name="." delimiter="." />
|
|
97
|
+
<FieldInput name={t('sign', 'Sign')} />
|
|
98
|
+
<FieldInput name={t('date', 'Date')} />
|
|
99
|
+
</div>
|
|
100
|
+
<div className={styles.cols4}>
|
|
101
|
+
<FieldInput name={t('nurseInCharge', 'Nurse In Charge')} />
|
|
102
|
+
<FieldInput name="." delimiter="." />
|
|
103
|
+
<FieldInput name={t('sign', 'Sign')} />
|
|
104
|
+
<FieldInput name={t('date', 'Date')} />
|
|
105
|
+
</div>
|
|
106
|
+
<div className={styles.cols4}>
|
|
107
|
+
<FieldInput name={t('securityGuard', 'Security Guard')} />
|
|
108
|
+
<FieldInput name="." delimiter="." />
|
|
109
|
+
<FieldInput name={t('sign', 'Sign')} />
|
|
110
|
+
<FieldInput name={t('date', 'Date')} />
|
|
111
|
+
</div>
|
|
112
|
+
<p>
|
|
113
|
+
<strong>{t('note', 'N/B')}:</strong>
|
|
114
|
+
<span>
|
|
115
|
+
{t(
|
|
116
|
+
'noteText',
|
|
117
|
+
'This form should be filled in duplicate, one copy to be retained in the ward and the other to be left in the main gate',
|
|
118
|
+
)}
|
|
119
|
+
</span>
|
|
120
|
+
</p>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default GatePassPrintout;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { InlineLoading, InlineNotification } from '@carbon/react';
|
|
2
|
+
import { type Encounter } from '@openmrs/esm-framework';
|
|
3
|
+
import type { Order } from '@openmrs/esm-patient-common-lib';
|
|
4
|
+
import React, { type FC, useMemo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { getObservationDisplayValue, useOrderConceptByUuid } from './discharge-printout.resource';
|
|
7
|
+
import styles from './discharge-printouts.scss';
|
|
8
|
+
type LabResultsProps = {
|
|
9
|
+
order: Order;
|
|
10
|
+
labEncounter?: Encounter;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const LabResults: FC<LabResultsProps> = ({ order, labEncounter: encounter }) => {
|
|
14
|
+
const { concept, isLoading: isLoadingTestConcepts, error } = useOrderConceptByUuid(order?.concept?.uuid);
|
|
15
|
+
const testResultObs = useMemo(() => {
|
|
16
|
+
if (!encounter || !concept) return null;
|
|
17
|
+
return encounter.obs?.find((obs) => obs.concept.uuid === concept.uuid);
|
|
18
|
+
}, [concept, encounter]);
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const EMPTY_TEXT = t('noResults', 'No results');
|
|
21
|
+
|
|
22
|
+
const testResults = useMemo(() => {
|
|
23
|
+
if (!concept) return [];
|
|
24
|
+
|
|
25
|
+
// For panel tests (with set members)
|
|
26
|
+
if (concept.setMembers && concept.setMembers.length > 0) {
|
|
27
|
+
return concept.setMembers.map((memberConcept) => {
|
|
28
|
+
const memberObs = testResultObs?.groupMembers?.find((obs) => obs.concept.uuid === memberConcept.uuid);
|
|
29
|
+
let resultValue: string;
|
|
30
|
+
if (memberObs) {
|
|
31
|
+
resultValue = getObservationDisplayValue(memberObs.value ?? (memberObs as any));
|
|
32
|
+
} else {
|
|
33
|
+
resultValue = EMPTY_TEXT;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
id: memberConcept.uuid,
|
|
38
|
+
testType: memberConcept.display || EMPTY_TEXT,
|
|
39
|
+
result: resultValue,
|
|
40
|
+
normalRange:
|
|
41
|
+
memberConcept.hiNormal && memberConcept.lowNormal
|
|
42
|
+
? `${memberConcept.lowNormal} - ${memberConcept.hiNormal}`
|
|
43
|
+
: 'N/A',
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// For single tests (no set members)
|
|
49
|
+
let resultValue: string;
|
|
50
|
+
if (testResultObs) {
|
|
51
|
+
resultValue = getObservationDisplayValue(testResultObs.value ?? (testResultObs as any));
|
|
52
|
+
} else {
|
|
53
|
+
resultValue = EMPTY_TEXT;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
id: concept.uuid,
|
|
59
|
+
testType: concept.display || EMPTY_TEXT,
|
|
60
|
+
result: resultValue,
|
|
61
|
+
normalRange: concept.hiNormal && concept.lowNormal ? `${concept.lowNormal} - ${concept.hiNormal}` : 'N/A',
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
}, [concept, testResultObs, EMPTY_TEXT]);
|
|
65
|
+
|
|
66
|
+
if (isLoadingTestConcepts)
|
|
67
|
+
return (
|
|
68
|
+
<InlineLoading
|
|
69
|
+
status="active"
|
|
70
|
+
iconDescription="Loading"
|
|
71
|
+
description={t('loadinglabresults', 'Loading lab results') + '...'}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (error)
|
|
76
|
+
return (
|
|
77
|
+
<InlineNotification
|
|
78
|
+
kind="error"
|
|
79
|
+
title={t('labResultError', 'Error loading lab results')}
|
|
80
|
+
subtitle={error?.message}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div key={order.uuid} className={styles.txtTitle}>
|
|
86
|
+
{testResults.map((res) => (
|
|
87
|
+
<p key={res.id}>
|
|
88
|
+
<strong>{res.testType.toLowerCase()}: </strong>
|
|
89
|
+
<span>{res.result}</span>
|
|
90
|
+
</p>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default LabResults;
|
|
@@ -94,10 +94,12 @@ export const useIpdDischargeEncounter = () => {
|
|
|
94
94
|
const { isLoadingLocation, location, errorFetchingLocation } = useWardLocation();
|
|
95
95
|
const pageSizes = [10, 20, 50, 100];
|
|
96
96
|
const [currPageSize, setCurrPageSize] = useState(10);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
|
|
98
|
+
const urls = useMemo(() => {
|
|
99
|
+
if (!location || !emrConfiguration?.exitFromInpatientEncounterType?.uuid) return null;
|
|
100
|
+
else
|
|
101
|
+
return `${fhirBaseUrl}/Encounter?_summary=data&type=${emrConfiguration?.exitFromInpatientEncounterType?.uuid}&location=${location?.uuid}`;
|
|
102
|
+
}, [location, emrConfiguration]);
|
|
101
103
|
const { data, isLoading, error, paginated, currentPage, goTo, totalCount, currentPageSize } =
|
|
102
104
|
useFhirPagination<Entry>(urls, currPageSize);
|
|
103
105
|
const encounters = useMemo(() => {
|
|
@@ -126,10 +128,10 @@ export const useIpdDischargeEncounter = () => {
|
|
|
126
128
|
};
|
|
127
129
|
};
|
|
128
130
|
|
|
129
|
-
export const useEncounterDetails = (encounterUuid: string) => {
|
|
130
|
-
const
|
|
131
|
-
'custom:(uuid,display,encounterDatetime,visit:(uuid,display,encounters:(uuid,display,encounterType:(uuid,display),encounterDatetime)))';
|
|
132
|
-
const url = `${restBaseUrl}/encounter/${encounterUuid}?v=${rep}`;
|
|
131
|
+
export const useEncounterDetails = (encounterUuid: string, rep?: string) => {
|
|
132
|
+
const _rep =
|
|
133
|
+
'custom:(uuid,display,location:(display),encounterDatetime,visit:(uuid,display,encounters:(uuid,display,encounterType:(uuid,display),encounterDatetime)))';
|
|
134
|
+
const url = `${restBaseUrl}/encounter/${encounterUuid}?v=${rep ?? _rep}`;
|
|
133
135
|
const { data, error, isLoading } = useSWR<FetchResponse<Encounter>>(url, openmrsFetch);
|
|
134
136
|
return {
|
|
135
137
|
isLoading,
|
package/src/index.ts
CHANGED
|
@@ -128,6 +128,10 @@ export const wardPatientWorkspaceBanner = getAsyncLifecycle(
|
|
|
128
128
|
() => import('./ward-workspace/patient-banner/patient-banner.component'),
|
|
129
129
|
options,
|
|
130
130
|
);
|
|
131
|
+
export const patientDischargeDocumentPreview = getAsyncLifecycle(
|
|
132
|
+
() => import('./discharge-printouts/discharge-printout.preview-modal'),
|
|
133
|
+
options,
|
|
134
|
+
);
|
|
131
135
|
|
|
132
136
|
export function startupApp() {
|
|
133
137
|
registerBreadcrumbs([]);
|
package/src/routes.json
CHANGED
|
@@ -85,6 +85,10 @@
|
|
|
85
85
|
"name": "maternal-ward",
|
|
86
86
|
"slot": "maternal-ward"
|
|
87
87
|
},
|
|
88
|
+
{
|
|
89
|
+
"component": "patientDischargeDocumentPreview",
|
|
90
|
+
"name": "patient-discharge-document-preview-modal"
|
|
91
|
+
},
|
|
88
92
|
{
|
|
89
93
|
"component": "wardPatientWorkspaceBanner",
|
|
90
94
|
"name": "ward-patient-workspace-banner",
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
parseDate,
|
|
19
19
|
useAppContext,
|
|
20
20
|
useConfig,
|
|
21
|
-
usePagination
|
|
21
|
+
usePagination,
|
|
22
22
|
} from '@openmrs/esm-framework';
|
|
23
23
|
import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
|
|
24
24
|
import dayjs from 'dayjs';
|
|
@@ -109,7 +109,7 @@ const AdmittedPatients = () => {
|
|
|
109
109
|
action: (
|
|
110
110
|
<OverflowMenu size={'sm'} flipped>
|
|
111
111
|
<OverflowMenuItem
|
|
112
|
-
itemText={t('
|
|
112
|
+
itemText={t('interWardTransfer', 'Interward Trasfer')}
|
|
113
113
|
onClick={() =>
|
|
114
114
|
launchWorkspace('patient-transfer-swap-workspace', {
|
|
115
115
|
workspaceTitle: 'Trasfer',
|
|
@@ -1,43 +1,40 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DataTable,
|
|
2
3
|
OverflowMenu,
|
|
3
4
|
OverflowMenuItem,
|
|
4
|
-
|
|
5
|
-
TableContainer,
|
|
5
|
+
Pagination,
|
|
6
6
|
Table,
|
|
7
|
-
TableHead,
|
|
8
|
-
TableRow,
|
|
9
|
-
TableHeader,
|
|
10
7
|
TableBody,
|
|
11
8
|
TableCell,
|
|
12
|
-
|
|
9
|
+
TableContainer,
|
|
10
|
+
TableHead,
|
|
11
|
+
TableHeader,
|
|
12
|
+
TableRow,
|
|
13
13
|
} from '@carbon/react';
|
|
14
|
-
import React, { useMemo, useState } from 'react';
|
|
15
|
-
import { useTranslation } from 'react-i18next';
|
|
16
|
-
import { EmptyState } from './table-state-components';
|
|
17
14
|
import {
|
|
18
15
|
type Encounter,
|
|
19
16
|
formatDatetime,
|
|
20
|
-
launchWorkspace,
|
|
21
|
-
type OpenmrsResource,
|
|
22
17
|
parseDate,
|
|
23
18
|
useAppContext,
|
|
24
19
|
useConfig,
|
|
25
20
|
useEmrConfiguration,
|
|
26
21
|
usePagination,
|
|
27
22
|
} from '@openmrs/esm-framework';
|
|
28
|
-
import { type WardPatient, type WardViewContext } from '../types';
|
|
29
|
-
import { bedLayoutToBed, getOpenmrsId } from '../ward-view/ward-view.resource';
|
|
30
|
-
import dayjs from 'dayjs';
|
|
31
23
|
import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
|
|
24
|
+
import dayjs from 'dayjs';
|
|
25
|
+
import React, { useMemo, useState } from 'react';
|
|
26
|
+
import { useTranslation } from 'react-i18next';
|
|
32
27
|
import { type WardConfigObject } from '../config-schema';
|
|
33
|
-
import {
|
|
28
|
+
import { type WardPatient, type WardViewContext } from '../types';
|
|
29
|
+
import { bedLayoutToBed, getOpenmrsId } from '../ward-view/ward-view.resource';
|
|
34
30
|
import { usePatientDischarge } from '../ward-workspace/kenya-emr-patient-discharge/patient-discharge.resource';
|
|
31
|
+
import { HyperLinkPatientCell, PatientBillStatus, UnAssignPatientBedAction } from './patient-cells';
|
|
32
|
+
import { EmptyState } from './table-state-components';
|
|
35
33
|
|
|
36
34
|
const DischargeInPatients = () => {
|
|
37
35
|
const { t } = useTranslation();
|
|
38
36
|
const { wardPatientGroupDetails } = useAppContext<WardViewContext>('ward-view-context') ?? {};
|
|
39
37
|
const { bedLayouts, wardAdmittedPatientsWithBed, isLoading } = wardPatientGroupDetails ?? {};
|
|
40
|
-
//TODO remove (added for demo purposes)
|
|
41
38
|
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
42
39
|
const { handleDischarge } = usePatientDischarge();
|
|
43
40
|
|
|
@@ -130,15 +127,22 @@ const DischargeInPatients = () => {
|
|
|
130
127
|
<UnAssignPatientBedAction
|
|
131
128
|
patientUuid={patient.patient.uuid}
|
|
132
129
|
encounterUuid={encounterAssigningToCurrentInpatientLocation?.uuid}
|
|
130
|
+
loading={wardPatientGroupDetails?.admissionLocationResponse?.isLoading || isLoadingEmrConfiguration}
|
|
133
131
|
onClick={async () => {
|
|
134
|
-
await handleDischarge(
|
|
132
|
+
await handleDischarge(
|
|
133
|
+
{} as Encounter,
|
|
134
|
+
patient,
|
|
135
|
+
emrConfiguration as Record<string, any>,
|
|
136
|
+
patient.visit,
|
|
137
|
+
wardPatientGroupDetails?.admissionLocationResponse?.admissionLocation?.ward,
|
|
138
|
+
);
|
|
135
139
|
}}
|
|
136
140
|
/>
|
|
137
141
|
</OverflowMenu>
|
|
138
142
|
),
|
|
139
143
|
};
|
|
140
144
|
});
|
|
141
|
-
}, [results, t, emrConfiguration, handleDischarge]);
|
|
145
|
+
}, [results, t, emrConfiguration, handleDischarge, wardPatientGroupDetails, isLoadingEmrConfiguration]);
|
|
142
146
|
|
|
143
147
|
if (!patients.length) return <EmptyState message={t('noDischargeInpatients', 'No Discharge in patients')} />;
|
|
144
148
|
|
|
@@ -16,7 +16,7 @@ import React, { useMemo } from 'react';
|
|
|
16
16
|
import { useTranslation } from 'react-i18next';
|
|
17
17
|
import { EmptyState, ErrorState } from './table-state-components';
|
|
18
18
|
import { useIpdDischargeEncounter } from '../hooks/useIpdDischargeEncounter';
|
|
19
|
-
import { formatDatetime, parseDate } from '@openmrs/esm-framework';
|
|
19
|
+
import { formatDatetime, parseDate, showModal } from '@openmrs/esm-framework';
|
|
20
20
|
import {
|
|
21
21
|
HyperLinkPatientCell,
|
|
22
22
|
PatientAdmissionDateCell,
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
PatientDayInWardCell,
|
|
25
25
|
PatientGenderCell,
|
|
26
26
|
} from './patient-cells';
|
|
27
|
+
import DischargeSummary from '../discharge-printouts/discharge-summary';
|
|
28
|
+
import GatePassPrintout from '../discharge-printouts/gate-pass-printout';
|
|
27
29
|
|
|
28
30
|
const DischargePatients = () => {
|
|
29
31
|
const { t } = useTranslation();
|
|
@@ -64,8 +66,26 @@ const DischargePatients = () => {
|
|
|
64
66
|
daysAdmitted: <PatientDayInWardCell patientUuid={encounter.patient.uuid} encounterUuid={encounter.uuid} />,
|
|
65
67
|
action: (
|
|
66
68
|
<OverflowMenu size={'sm'} flipped>
|
|
67
|
-
<OverflowMenuItem
|
|
68
|
-
|
|
69
|
+
<OverflowMenuItem
|
|
70
|
+
itemText={t('dischargeSummary', 'Discharge Summary')}
|
|
71
|
+
onClick={() => {
|
|
72
|
+
const dispose = showModal('patient-discharge-document-preview-modal', {
|
|
73
|
+
size: 'lg',
|
|
74
|
+
onClose: () => dispose(),
|
|
75
|
+
printout: <DischargeSummary dischargeEncounterUuid={encounter.uuid} patient={encounter.patient} />,
|
|
76
|
+
});
|
|
77
|
+
}}
|
|
78
|
+
/>
|
|
79
|
+
<OverflowMenuItem
|
|
80
|
+
itemText={t('gatePass', 'Gate Pass')}
|
|
81
|
+
onClick={() => {
|
|
82
|
+
const dispose = showModal('patient-discharge-document-preview-modal', {
|
|
83
|
+
size: 'lg',
|
|
84
|
+
onClose: () => dispose(),
|
|
85
|
+
printout: <GatePassPrintout dischargeEncounterUuid={encounter.uuid} patient={encounter.patient} />,
|
|
86
|
+
});
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
69
89
|
</OverflowMenu>
|
|
70
90
|
),
|
|
71
91
|
};
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import { InlineLoading, OverflowMenuItem, Tag } from '@carbon/react';
|
|
2
|
-
import {
|
|
3
|
-
ConfigurableLink,
|
|
4
|
-
formatDatetime,
|
|
5
|
-
parseDate,
|
|
6
|
-
useConfig,
|
|
7
|
-
useEmrConfiguration,
|
|
8
|
-
usePatient,
|
|
9
|
-
} from '@openmrs/esm-framework';
|
|
2
|
+
import { ConfigurableLink, formatDatetime, parseDate, useEmrConfiguration, usePatient } from '@openmrs/esm-framework';
|
|
10
3
|
import dayjs from 'dayjs';
|
|
11
4
|
import React, { type FC, useMemo } from 'react';
|
|
12
5
|
import { useTranslation } from 'react-i18next';
|
|
13
|
-
import { type WardConfigObject } from '../config-schema';
|
|
14
6
|
import { useEncounterDetails } from '../hooks/useIpdDischargeEncounter';
|
|
15
7
|
import { usePatientBills } from '../ward-workspace/kenya-emr-patient-discharge/patient-discharge.resource';
|
|
16
8
|
|
|
@@ -127,11 +119,13 @@ export const PatientBillStatus: FC<PatientAdmissionCellProps> = ({ patientUuid,
|
|
|
127
119
|
|
|
128
120
|
type UnAssignPatientBedActionProps = PatientAdmissionCellProps & {
|
|
129
121
|
onClick?: () => void;
|
|
122
|
+
loading?: boolean;
|
|
130
123
|
};
|
|
131
124
|
export const UnAssignPatientBedAction: FC<UnAssignPatientBedActionProps> = ({
|
|
132
125
|
encounterUuid,
|
|
133
126
|
patientUuid,
|
|
134
127
|
onClick,
|
|
128
|
+
loading,
|
|
135
129
|
}) => {
|
|
136
130
|
const { encounter, error, isLoading } = useEncounterDetails(encounterUuid);
|
|
137
131
|
const { t } = useTranslation();
|
|
@@ -156,7 +150,7 @@ export const UnAssignPatientBedAction: FC<UnAssignPatientBedActionProps> = ({
|
|
|
156
150
|
dailyBedFeeSettled,
|
|
157
151
|
} = usePatientBills(patientUuid, startDate?.toDate(), endDateDate.toDate());
|
|
158
152
|
|
|
159
|
-
if (isLoading || isLoadingBills) return <InlineLoading />;
|
|
153
|
+
if (isLoading || isLoadingBills || loading) return <InlineLoading />;
|
|
160
154
|
if (error || billsError) return null;
|
|
161
155
|
if (bills.length === 0) return null;
|
|
162
156
|
if (pendingBills.length > 0) return null;
|