@kenyaemr/esm-ward-app 8.0.1-pre.99 → 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.
- package/.turbo/turbo-build.log +20 -24
- package/dist/109.js +1 -0
- package/dist/109.js.map +1 -0
- package/dist/124.js +1 -0
- package/dist/124.js.map +1 -0
- package/dist/125.js +1 -0
- package/dist/125.js.map +1 -0
- package/dist/130.js +1 -1
- package/dist/130.js.LICENSE.txt +2 -0
- package/dist/130.js.map +1 -1
- package/dist/146.js +1 -0
- package/dist/146.js.map +1 -0
- package/dist/15.js +1 -0
- package/dist/15.js.map +1 -0
- package/dist/153.js +1 -0
- package/dist/153.js.map +1 -0
- package/dist/303.js +2 -1
- package/dist/303.js.map +1 -1
- package/dist/325.js +1 -0
- package/dist/325.js.map +1 -0
- package/dist/372.js +2 -0
- package/dist/372.js.map +1 -0
- package/dist/471.js +1 -0
- package/dist/471.js.map +1 -0
- package/dist/481.js +1 -0
- package/dist/481.js.map +1 -0
- package/dist/53.js +1 -0
- package/dist/53.js.map +1 -0
- package/dist/{960.js → 559.js} +1 -1
- package/dist/559.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/576.js +1 -0
- package/dist/576.js.map +1 -0
- package/dist/577.js +1 -1
- package/dist/577.js.map +1 -1
- package/dist/{255.js → 649.js} +2 -2
- package/dist/649.js.LICENSE.txt +9 -0
- package/dist/649.js.map +1 -0
- package/dist/{659.js → 662.js} +1 -1
- package/dist/662.js.map +1 -0
- package/dist/920.js +1 -0
- package/dist/920.js.map +1 -0
- package/dist/921.js +1 -0
- package/dist/921.js.map +1 -0
- package/dist/922.js +1 -0
- package/dist/922.js.map +1 -0
- package/dist/969.js +1 -0
- package/dist/969.js.map +1 -0
- package/dist/kenyaemr-esm-ward-app.js +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +304 -128
- package/dist/kenyaemr-esm-ward-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +0 -10
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/mock.tsx +62 -0
- package/package-lock.json +5001 -0
- package/package.json +2 -3
- package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +37 -0
- package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +20 -0
- package/src/beds/empty-bed-skeleton.tsx +4 -3
- package/src/beds/empty-bed.component.tsx +3 -3
- package/src/beds/ward-bed.component.tsx +41 -0
- package/src/beds/ward-bed.scss +45 -0
- package/src/beds/{occupied-bed.test.tsx → ward-bed.test.tsx} +42 -20
- package/src/config-schema.ts +203 -84
- package/src/constant.ts +1 -1
- package/src/hooks/useAdmissionLocation.ts +22 -4
- package/src/hooks/useAssignedBedByPatient.ts +9 -0
- package/src/hooks/useBeds.ts +3 -4
- package/src/hooks/useConcept.ts +3 -4
- package/src/hooks/useEmrConfiguration.ts +5 -0
- package/src/hooks/useInpatientAdmission.ts +9 -14
- package/src/hooks/useInpatientRequest.ts +4 -15
- package/src/hooks/useLocations.ts +8 -51
- package/src/hooks/useMotherAndChildren.ts +46 -0
- package/src/hooks/useObs.ts +3 -7
- package/src/hooks/usePatientPendingOrders.ts +16 -0
- package/src/hooks/useWardPatientGrouping.ts +32 -0
- package/src/index.ts +45 -17
- package/src/location-selector/location-selector.component.tsx +18 -21
- package/src/root.component.tsx +3 -0
- package/src/routes.json +41 -3
- package/src/types/index.ts +50 -1
- package/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx +38 -0
- package/src/ward-patient-card/card-rows/coded-obs-tags-row.component.tsx +108 -0
- package/src/ward-patient-card/card-rows/mother-child-row.component.tsx +84 -0
- package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
- package/src/ward-patient-card/card-rows/pending-items-row.component.tsx +54 -0
- package/src/ward-patient-card/row-elements/ward-patient-age.tsx +1 -1
- package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +62 -39
- package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +5 -5
- package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +18 -41
- package/src/ward-patient-card/row-elements/ward-patient-location.tsx +19 -0
- package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +38 -34
- package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +26 -13
- package/src/ward-patient-card/row-elements/ward-patient-pending-order.component.tsx +45 -0
- package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.tsx +38 -0
- package/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx +32 -0
- package/src/ward-patient-card/row-elements/ward-patient-skeleton-text.tsx +9 -0
- package/src/ward-patient-card/ward-patient-card.component.tsx +14 -45
- package/src/ward-patient-card/ward-patient-card.scss +68 -9
- package/src/ward-view/default-ward/default-ward-beds.component.tsx +42 -0
- package/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx +32 -0
- package/src/ward-view/default-ward/default-ward-patient-card.component.tsx +31 -0
- package/src/ward-view/default-ward/default-ward-pending-patients.component.tsx +52 -0
- package/src/ward-view/default-ward/default-ward-unassigned-patients.component.tsx +32 -0
- package/src/ward-view/default-ward/default-ward-view.component.tsx +31 -0
- package/src/ward-view/materal-ward/maternal-ward-beds.component.tsx +65 -0
- package/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx +30 -0
- package/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx +93 -0
- package/src/{beds/occupied-bed.scss → ward-view/materal-ward/maternal-ward-patient-card.scss} +4 -9
- package/src/ward-view/materal-ward/maternal-ward-patient-card.test.tsx +58 -0
- package/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx +48 -0
- package/src/ward-view/materal-ward/maternal-ward-unassigned-patients.component.tsx +33 -0
- package/src/ward-view/materal-ward/maternal-ward-view.component.tsx +38 -0
- package/src/ward-view/materal-ward/maternal-ward-view.resource.ts +89 -0
- package/src/ward-view/ward-view.component.tsx +15 -163
- package/src/ward-view/ward-view.resource.ts +193 -1
- package/src/ward-view/ward-view.scss +17 -6
- package/src/ward-view/ward-view.test.tsx +43 -48
- package/src/ward-view/ward.component.tsx +106 -0
- package/src/ward-view-header/admission-requests-bar.component.tsx +14 -9
- package/src/ward-view-header/admission-requests-bar.test.tsx +11 -23
- package/src/ward-view-header/admission-requests.scss +1 -1
- package/src/ward-view-header/ward-metric.component.tsx +24 -0
- package/src/ward-view-header/ward-metric.scss +25 -0
- package/src/ward-view-header/ward-metrics.component.tsx +78 -0
- package/src/ward-view-header/ward-metrics.scss +7 -0
- package/src/ward-view-header/ward-metrics.test.tsx +37 -0
- package/src/ward-view-header/ward-view-header.component.tsx +9 -4
- package/src/ward-view-header/ward-view-header.scss +0 -1
- package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +70 -6
- package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +10 -23
- package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +9 -3
- package/src/ward-workspace/admission-request-card/admission-request-card.scss +13 -4
- package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +55 -33
- package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +30 -37
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +98 -203
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +116 -180
- package/src/ward-workspace/bed-selector.component.tsx +119 -0
- package/src/ward-workspace/bed-selector.scss +15 -0
- package/src/ward-workspace/patient-banner/patient-banner.component.tsx +7 -14
- package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +23 -0
- package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient-action-button.extension.tsx +2 -1
- package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.workspace.tsx +18 -9
- package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
- package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +113 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +68 -79
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +24 -24
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +12 -2
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +12 -8
- package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +11 -0
- package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +2 -2
- package/src/ward-workspace/ward-patient-notes/notes.resource.ts +5 -7
- package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/types.ts +0 -4
- package/src/ward.resource.ts +38 -2
- package/translations/en.json +31 -7
- package/dist/152.js +0 -1
- package/dist/152.js.map +0 -1
- package/dist/255.js.map +0 -1
- package/dist/269.js +0 -1
- package/dist/269.js.map +0 -1
- package/dist/346.js +0 -1
- package/dist/346.js.map +0 -1
- package/dist/466.js +0 -1
- package/dist/466.js.map +0 -1
- package/dist/659.js.map +0 -1
- package/dist/729.js +0 -1
- package/dist/729.js.map +0 -1
- package/dist/749.js +0 -1
- package/dist/749.js.map +0 -1
- package/dist/76.js +0 -1
- package/dist/76.js.map +0 -1
- package/dist/793.js +0 -2
- package/dist/793.js.map +0 -1
- package/dist/803.js +0 -1
- package/dist/803.js.map +0 -1
- package/dist/960.js.map +0 -1
- package/src/beds/empty-bed.scss +0 -28
- package/src/beds/occupied-bed.component.tsx +0 -35
- package/src/beds/unassigned-patient.component.tsx +0 -20
- package/src/beds/unassigned-patient.scss +0 -6
- package/src/config-schema-admission-request-note.ts +0 -9
- package/src/config-schema-extension-colored-obs-tags.ts +0 -91
- package/src/hooks/useCurrentWardCardConfig.ts +0 -32
- package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +0 -27
- package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +0 -13
- package/src/ward-patient-card/ward-patient-card-element.component.tsx +0 -65
- package/src/ward-view/ward-bed.component.tsx +0 -14
- /package/dist/{793.js.LICENSE.txt → 303.js.LICENSE.txt} +0 -0
- /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
- /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
3
|
+
|
|
4
|
+
.motherOrBabyRow {
|
|
5
|
+
display: flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.motherOrBabyRowElementsDiv {
|
|
9
|
+
flex: 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.motherOrBabyIconDiv {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.motherOrBabyIcon {
|
|
18
|
+
padding: layout.$spacing-02;
|
|
19
|
+
border-radius: 50%;
|
|
20
|
+
background-color: $grey-2;
|
|
21
|
+
margin-right: layout.$spacing-03;
|
|
22
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Hourglass } from '@carbon/react/icons';
|
|
2
|
+
import React, { useCallback, useEffect } from 'react';
|
|
3
|
+
import { type WardPatient } from '../../types';
|
|
4
|
+
|
|
5
|
+
import { useElementConfig } from '../../ward-view/ward-view.resource';
|
|
6
|
+
import { WardPatientPendingOrder } from '../row-elements/ward-patient-pending-order.component';
|
|
7
|
+
import WardPatientPendingTransfer from '../row-elements/ward-patient-pending-transfer';
|
|
8
|
+
import styles from '../ward-patient-card.scss';
|
|
9
|
+
|
|
10
|
+
export interface PendingItemsRowProps {
|
|
11
|
+
id: string;
|
|
12
|
+
wardPatient: WardPatient;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const PendingItemsRow: React.FC<PendingItemsRowProps> = ({ id, wardPatient }) => {
|
|
16
|
+
const { orders, showPendingItems } = useElementConfig('pendingItems', id);
|
|
17
|
+
const [hasPendingOrders, setHasPendingOrders] = React.useState(false);
|
|
18
|
+
|
|
19
|
+
const hasPendingItems = !!wardPatient?.inpatientRequest || hasPendingOrders;
|
|
20
|
+
|
|
21
|
+
const handlePendingOrderCount = useCallback((count: number) => {
|
|
22
|
+
if (count > 0) {
|
|
23
|
+
setHasPendingOrders(true);
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!orders?.orderTypes?.length) {
|
|
29
|
+
setHasPendingOrders(false);
|
|
30
|
+
}
|
|
31
|
+
}, [orders]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={styles.wardPatientCardPendingItemsRow}>
|
|
35
|
+
{showPendingItems && hasPendingItems ? (
|
|
36
|
+
<>
|
|
37
|
+
<Hourglass className={styles.hourGlassIcon} size="16" />:
|
|
38
|
+
</>
|
|
39
|
+
) : null}
|
|
40
|
+
{orders?.orderTypes.map(({ uuid, label }) => (
|
|
41
|
+
<WardPatientPendingOrder
|
|
42
|
+
key={`pending-order-type-${uuid}`}
|
|
43
|
+
wardPatient={wardPatient}
|
|
44
|
+
orderUuid={uuid}
|
|
45
|
+
label={label}
|
|
46
|
+
onOrderCount={handlePendingOrderCount}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
{wardPatient?.inpatientRequest ? <WardPatientPendingTransfer wardPatient={wardPatient} /> : null}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default PendingItemsRow;
|
|
@@ -6,7 +6,7 @@ export interface WardPatientAgeProps {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const WardPatientAge: React.FC<WardPatientAgeProps> = ({ patient }) => {
|
|
9
|
-
return <div>{age(patient.person
|
|
9
|
+
return patient.person?.birthdate ? <div>{age(patient.person.birthdate)}</div> : null;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export default WardPatientAge;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
3
|
-
import React from 'react';
|
|
1
|
+
import { Tag } from '@carbon/react';
|
|
2
|
+
import { type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework';
|
|
3
|
+
import React, { type ReactNode } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { moduleName } from '../../constant';
|
|
6
5
|
import { useObs } from '../../hooks/useObs';
|
|
6
|
+
import { useElementConfig } from '../../ward-view/ward-view.resource';
|
|
7
7
|
import styles from '../ward-patient-card.scss';
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
8
|
+
import WardPatientSkeletonText from './ward-patient-skeleton-text';
|
|
9
|
+
import { getObsEncounterString, obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource';
|
|
10
|
+
import WardPatientResponsiveTooltip from './ward-patient-responsive-tooltip';
|
|
10
11
|
|
|
11
12
|
interface WardPatientCodedObsTagsProps {
|
|
12
|
-
|
|
13
|
+
id: string;
|
|
13
14
|
patient: Patient;
|
|
14
15
|
visit: Visit;
|
|
15
16
|
}
|
|
@@ -24,50 +25,72 @@ interface WardPatientCodedObsTagsProps {
|
|
|
24
25
|
* @param config
|
|
25
26
|
* @returns
|
|
26
27
|
*/
|
|
27
|
-
const WardPatientCodedObsTags: React.FC<WardPatientCodedObsTagsProps> = ({
|
|
28
|
-
const
|
|
29
|
-
const {
|
|
28
|
+
const WardPatientCodedObsTags: React.FC<WardPatientCodedObsTagsProps> = ({ id, patient, visit }) => {
|
|
29
|
+
const config = useElementConfig('coloredObsTags', id);
|
|
30
|
+
const { conceptUuid, summaryLabel, summaryLabelColor } = config ?? {};
|
|
31
|
+
const { data, isLoading } = useObs(
|
|
32
|
+
{ patient: patient.uuid, concept: conceptUuid },
|
|
33
|
+
conceptUuid != null,
|
|
34
|
+
obsCustomRepresentation,
|
|
35
|
+
);
|
|
30
36
|
const { t } = useTranslation();
|
|
31
|
-
const
|
|
37
|
+
const conceptToTagColorMap = useConceptToTagColorMap(config?.tags ?? []);
|
|
32
38
|
|
|
33
39
|
if (isLoading) {
|
|
34
|
-
return
|
|
40
|
+
return (
|
|
41
|
+
<div className={styles.wardPatientCardRow}>
|
|
42
|
+
<WardPatientSkeletonText />
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
35
45
|
} else {
|
|
36
|
-
const obsToDisplay = data?.
|
|
46
|
+
const obsToDisplay = data?.filter((o) => {
|
|
37
47
|
const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
|
|
38
|
-
return matchVisit
|
|
48
|
+
return matchVisit;
|
|
39
49
|
});
|
|
40
50
|
|
|
41
|
-
const summaryLabelToDisplay =
|
|
42
|
-
summaryLabel != null
|
|
43
|
-
? translateFrom(summaryLabelI18nModule ?? moduleName, summaryLabel)
|
|
44
|
-
: obsToDisplay?.[0]?.concept?.display;
|
|
51
|
+
const summaryLabelToDisplay = summaryLabel != null ? t(summaryLabel) : obsToDisplay?.[0]?.concept?.display;
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
// for each obs configured to be displayed with a color, we create a tag for it
|
|
54
|
+
// for other obs not configured, we create a single summary tag for all of them.
|
|
55
|
+
const summaryTagTooltipText: ReactNode[] = [];
|
|
56
|
+
const coloredOpsTags = obsToDisplay
|
|
57
|
+
?.map((o) => {
|
|
58
|
+
const { display, uuid } = o.value as OpenmrsResource;
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const color = conceptToTagColorMap?.get(uuid);
|
|
61
|
+
if (color) {
|
|
62
|
+
return (
|
|
63
|
+
<WardPatientResponsiveTooltip tooltipContent={getObsEncounterString(o, t)}>
|
|
64
|
+
<Tag type={color} key={`ward-coded-obs-tag-${o.uuid}`}>
|
|
65
|
+
{display}
|
|
66
|
+
</Tag>
|
|
67
|
+
</WardPatientResponsiveTooltip>
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
summaryTagTooltipText.push(
|
|
71
|
+
<div key={uuid}>
|
|
72
|
+
{display} ({getObsEncounterString(o, t)})
|
|
73
|
+
</div>,
|
|
74
|
+
);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.filter((o) => o != null);
|
|
60
79
|
|
|
61
|
-
|
|
62
|
-
if (obsNodes?.length > 0 || obsWithNoTagCount > 0) {
|
|
80
|
+
if (coloredOpsTags?.length > 0 || summaryTagTooltipText.length > 0) {
|
|
63
81
|
return (
|
|
64
|
-
<div>
|
|
82
|
+
<div className={styles.wardPatientCardRow}>
|
|
65
83
|
<span className={styles.wardPatientObsLabel}>
|
|
66
|
-
{
|
|
67
|
-
{
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
{coloredOpsTags}
|
|
85
|
+
{summaryTagTooltipText.length > 0 ? (
|
|
86
|
+
<WardPatientResponsiveTooltip tooltipContent={summaryTagTooltipText}>
|
|
87
|
+
<Tag type={summaryLabelColor}>
|
|
88
|
+
{t('countItems', '{{count}} {{item}}', {
|
|
89
|
+
count: summaryTagTooltipText.length,
|
|
90
|
+
item: summaryLabelToDisplay,
|
|
91
|
+
})}
|
|
92
|
+
</Tag>
|
|
93
|
+
</WardPatientResponsiveTooltip>
|
|
71
94
|
) : null}
|
|
72
95
|
</span>
|
|
73
96
|
</div>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import styles from '../ward-patient-card.scss';
|
|
3
1
|
import { type Patient } from '@openmrs/esm-framework';
|
|
4
|
-
import
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useElementConfig } from '../../ward-view/ward-view.resource';
|
|
5
4
|
|
|
6
5
|
export interface WardPatientAddressProps {
|
|
7
6
|
patient: Patient;
|
|
8
|
-
|
|
7
|
+
id: string;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
const WardPatientAddress: React.FC<WardPatientAddressProps> = ({ patient,
|
|
10
|
+
const WardPatientAddress: React.FC<WardPatientAddressProps> = ({ patient, id }) => {
|
|
12
11
|
const preferredAddress = patient?.person?.preferredAddress;
|
|
12
|
+
const config = useElementConfig("patientAddress", id);
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
15
|
<>
|
|
@@ -1,52 +1,29 @@
|
|
|
1
|
+
import { type Patient, type PatientIdentifier, PatientBannerPatientIdentifier } from '@openmrs/esm-framework';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { Tag } from '@carbon/react';
|
|
4
|
-
import { type Patient, translateFrom, type PatientIdentifier } from '@openmrs/esm-framework';
|
|
5
|
-
import { moduleName } from '../../constant';
|
|
6
|
-
import { useTranslation } from 'react-i18next';
|
|
7
|
-
|
|
8
|
-
/** Sort the identifiers by preferred first. The identifier with value of true
|
|
9
|
-
* takes precedence over false. If both identifiers have same preferred value,
|
|
10
|
-
* sort them by most recently created or changed. */
|
|
11
|
-
const identifierCompareFunction = (pi1: PatientIdentifier, pi2: PatientIdentifier) => {
|
|
12
|
-
let comp = (pi2.preferred ? 1 : 0) - (pi1.preferred ? 1 : 0);
|
|
13
|
-
|
|
14
|
-
if (comp == 0) {
|
|
15
|
-
const date1 = pi1.auditInfo.dateChanged ?? pi1.auditInfo.dateCreated;
|
|
16
|
-
const date2 = pi2.auditInfo.dateChanged ?? pi2.auditInfo.dateCreated;
|
|
17
|
-
comp = date2.localeCompare(date1);
|
|
18
|
-
}
|
|
19
|
-
return comp;
|
|
20
|
-
};
|
|
3
|
+
import { useElementConfig } from '../../ward-view/ward-view.resource';
|
|
21
4
|
|
|
22
5
|
export interface WardPatientIdentifierProps {
|
|
23
6
|
patient: Patient;
|
|
24
|
-
|
|
25
|
-
config?: IdentifierElementDefinition;
|
|
7
|
+
id?: string;
|
|
26
8
|
}
|
|
27
9
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
const WardPatientIdentifier: React.FC<WardPatientIdentifierProps> = ({ id, patient }) => {
|
|
11
|
+
const config = useElementConfig('patientIdentifier', id);
|
|
12
|
+
|
|
13
|
+
const fhirIdentifiers: fhir.Identifier[] = patient.identifiers.map((identifier: PatientIdentifier) => ({
|
|
14
|
+
value: identifier.identifier,
|
|
15
|
+
type: {
|
|
16
|
+
text: identifier.identifierType.name,
|
|
17
|
+
coding: [
|
|
18
|
+
{
|
|
19
|
+
code: identifier.identifierType.uuid,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
32
24
|
|
|
33
|
-
const WardPatientIdentifier: React.FC<WardPatientIdentifierProps> = ({ config: configProp, patient }) => {
|
|
34
|
-
const { t } = useTranslation();
|
|
35
|
-
const config = configProp ?? defaultConfig;
|
|
36
|
-
const { identifierTypeUuid, labelI18nModule: labelModule, label } = config;
|
|
37
|
-
const patientIdentifiers = patient.identifiers.filter(
|
|
38
|
-
(patientIdentifier: PatientIdentifier) =>
|
|
39
|
-
identifierTypeUuid == null || patientIdentifier.identifierType?.uuid === identifierTypeUuid,
|
|
40
|
-
);
|
|
41
|
-
patientIdentifiers.sort(identifierCompareFunction);
|
|
42
|
-
const patientIdentifier = patientIdentifiers[0];
|
|
43
|
-
const labelToDisplay =
|
|
44
|
-
label != null ? translateFrom(labelModule ?? moduleName, label) : patientIdentifier?.identifierType?.name;
|
|
45
25
|
return (
|
|
46
|
-
<
|
|
47
|
-
{labelToDisplay ? <Tag>{t('identifierTypelabel', '{{label}}:', { label: labelToDisplay })}</Tag> : <></>}
|
|
48
|
-
<span>{patientIdentifier?.identifier}</span>
|
|
49
|
-
</div>
|
|
26
|
+
<PatientBannerPatientIdentifier identifier={fhirIdentifiers} showIdentifierLabel={config?.showIdentifierLabel} />
|
|
50
27
|
);
|
|
51
28
|
};
|
|
52
29
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type InpatientAdmission } from '../../types';
|
|
3
|
+
|
|
4
|
+
export interface WardPatientIdentifierProps {
|
|
5
|
+
inpatientAdmission: InpatientAdmission;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const WardPatientLocation: React.FC<WardPatientIdentifierProps> = ({ inpatientAdmission }) => {
|
|
9
|
+
const locationDisplay = inpatientAdmission?.encounterAssigningToCurrentInpatientLocation.location?.display;
|
|
10
|
+
return locationDisplay ? (
|
|
11
|
+
<div>
|
|
12
|
+
<span>{locationDisplay}</span>
|
|
13
|
+
</div>
|
|
14
|
+
) : (
|
|
15
|
+
<></>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default WardPatientLocation;
|
|
@@ -1,52 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { type
|
|
1
|
+
import { restBaseUrl, useOpenmrsFetchAll, type Concept } from '@openmrs/esm-framework';
|
|
2
|
+
import { type Observation } from '../../types';
|
|
3
|
+
import { type TFunction } from 'i18next';
|
|
4
|
+
import { type ColoredObsTagConfig } from '../../config-schema';
|
|
4
5
|
|
|
5
6
|
// prettier-ignore
|
|
6
7
|
export const obsCustomRepresentation =
|
|
7
8
|
'custom:(uuid,display,obsDatetime,value,' +
|
|
8
9
|
'concept:(uuid,display),' +
|
|
9
|
-
'encounter:(uuid,display,' +
|
|
10
|
+
'encounter:(uuid,display,encounterType,encounterDatetime,' +
|
|
10
11
|
'visit:(uuid,display)))';
|
|
11
12
|
|
|
12
13
|
// get the setMembers of a concept set
|
|
13
14
|
const conceptSetCustomRepresentation = 'custom:(uuid,setMembers:(uuid))';
|
|
14
15
|
|
|
15
|
-
export function useConceptToTagColorMap(tags: Array<
|
|
16
|
-
//
|
|
17
|
-
// to
|
|
18
|
-
//
|
|
19
|
-
const fetchAndMap = (url: string) => {
|
|
20
|
-
const conceptSetToTagColorMap = new Map<string, string>();
|
|
21
|
-
for (const tag of tags) {
|
|
22
|
-
const { color, appliedToConceptSets } = tag;
|
|
23
|
-
for (const answer of appliedToConceptSets ?? []) {
|
|
24
|
-
if (!conceptSetToTagColorMap.has(answer)) {
|
|
25
|
-
conceptSetToTagColorMap.set(answer, color);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
16
|
+
export function useConceptToTagColorMap(tags: Array<ColoredObsTagConfig> = []) {
|
|
17
|
+
// The TacConfigObject allows us to specify the mapping of
|
|
18
|
+
// concept sets to colors. However, we also need to build a map of
|
|
19
|
+
// concepts to colors. This function does that.
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
conceptToTagColorMap.set(concept.uuid, conceptSetToTagColorMap.get(conceptSet.uuid));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
21
|
+
// TODO: We should cache this map to be re-usable app-wide
|
|
22
|
+
const conceptSetToTagColorMap = new Map<string, string>();
|
|
23
|
+
for (const tag of tags) {
|
|
24
|
+
const { color, appliedToConceptSets } = tag;
|
|
25
|
+
for (const answer of appliedToConceptSets ?? []) {
|
|
26
|
+
if (!conceptSetToTagColorMap.has(answer)) {
|
|
27
|
+
conceptSetToTagColorMap.set(answer, color);
|
|
41
28
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
46
31
|
|
|
47
32
|
const conceptSetUuids = tags.flatMap((tag) => tag.appliedToConceptSets);
|
|
48
33
|
const apiUrl = `${restBaseUrl}/concept?references=${conceptSetUuids.join()}&v=${conceptSetCustomRepresentation}`;
|
|
49
|
-
const
|
|
34
|
+
const { data: conceptSets } = useOpenmrsFetchAll<Concept>(apiUrl);
|
|
35
|
+
|
|
36
|
+
const conceptToTagColorMap = new Map<string, string>();
|
|
37
|
+
if (conceptSets) {
|
|
38
|
+
for (const conceptSet of conceptSets) {
|
|
39
|
+
for (const concept of conceptSet.setMembers) {
|
|
40
|
+
if (!conceptToTagColorMap.has(concept.uuid)) {
|
|
41
|
+
conceptToTagColorMap.set(concept.uuid, conceptSetToTagColorMap.get(conceptSet.uuid));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
50
46
|
|
|
51
47
|
return conceptToTagColorMap;
|
|
52
48
|
}
|
|
49
|
+
|
|
50
|
+
export function getObsEncounterString(obs: Observation, t: TFunction) {
|
|
51
|
+
return t('encounterDisplay', '{{encounterType}} {{encounterDate}}', {
|
|
52
|
+
encounterType: obs.encounter.encounterType.display,
|
|
53
|
+
encounterDate: new Date(obs.encounter.encounterDatetime).toLocaleDateString(),
|
|
54
|
+
interpolation: { escapeValue: false },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
import { SkeletonText } from '@carbon/react';
|
|
2
|
-
import { type OpenmrsResource, type Patient,
|
|
2
|
+
import { type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { type
|
|
5
|
+
import { type ObsElementConfig } from '../../config-schema';
|
|
6
6
|
import { useObs } from '../../hooks/useObs';
|
|
7
7
|
import styles from '../ward-patient-card.scss';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { getObsEncounterString, obsCustomRepresentation } from './ward-patient-obs.resource';
|
|
9
|
+
import WardPatientResponsiveTooltip from './ward-patient-responsive-tooltip';
|
|
10
|
+
import { useElementConfig } from '../../ward-view/ward-view.resource';
|
|
10
11
|
|
|
11
12
|
export interface WardPatientObsProps {
|
|
12
|
-
|
|
13
|
+
id: string;
|
|
14
|
+
configOverride?: ObsElementConfig;
|
|
13
15
|
patient: Patient;
|
|
14
16
|
visit: Visit;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
const WardPatientObs: React.FC<WardPatientObsProps> = ({
|
|
18
|
-
const
|
|
19
|
-
const
|
|
19
|
+
const WardPatientObs: React.FC<WardPatientObsProps> = ({ id, configOverride, patient, visit }) => {
|
|
20
|
+
const config: ObsElementConfig = useElementConfig('obs', id);
|
|
21
|
+
const configToUse = configOverride ?? config;
|
|
22
|
+
const { conceptUuid, onlyWithinCurrentVisit, orderBy, limit, label } = configToUse ?? {};
|
|
23
|
+
const { data, isLoading } = useObs(
|
|
24
|
+
{ patient: patient.uuid, concept: conceptUuid },
|
|
25
|
+
conceptUuid != null,
|
|
26
|
+
obsCustomRepresentation,
|
|
27
|
+
);
|
|
20
28
|
const { t } = useTranslation();
|
|
21
29
|
|
|
22
30
|
if (isLoading) {
|
|
23
31
|
return <SkeletonText />;
|
|
24
32
|
} else {
|
|
25
|
-
const obsToDisplay = data
|
|
33
|
+
const obsToDisplay = data
|
|
26
34
|
?.filter((o) => {
|
|
27
35
|
const matchVisit = !onlyWithinCurrentVisit || o.encounter.visit?.uuid == visit?.uuid;
|
|
28
36
|
return matchVisit;
|
|
@@ -32,13 +40,18 @@ const WardPatientObs: React.FC<WardPatientObsProps> = ({ config, patient, visit
|
|
|
32
40
|
})
|
|
33
41
|
?.slice(0, limit == 0 ? Number.MAX_VALUE : limit);
|
|
34
42
|
|
|
35
|
-
const labelToDisplay =
|
|
36
|
-
label != null ? translateFrom(labelModule ?? moduleName, label) : obsToDisplay?.[0]?.concept?.display;
|
|
43
|
+
const labelToDisplay = label != null ? t(label) : obsToDisplay?.[0]?.concept?.display;
|
|
37
44
|
|
|
38
45
|
const obsNodes = obsToDisplay?.map((o) => {
|
|
39
46
|
const { value } = o;
|
|
40
47
|
const display: any = (value as OpenmrsResource)?.display ?? o.value;
|
|
41
|
-
|
|
48
|
+
|
|
49
|
+
const tooltipContent = getObsEncounterString(o, t);
|
|
50
|
+
return (
|
|
51
|
+
<WardPatientResponsiveTooltip key={o.uuid} tooltipContent={tooltipContent}>
|
|
52
|
+
<span title={tooltipContent}>{display} </span>
|
|
53
|
+
</WardPatientResponsiveTooltip>
|
|
54
|
+
);
|
|
42
55
|
});
|
|
43
56
|
|
|
44
57
|
if (obsNodes?.length > 0) {
|
|
@@ -47,7 +60,7 @@ const WardPatientObs: React.FC<WardPatientObsProps> = ({ config, patient, visit
|
|
|
47
60
|
<span className={styles.wardPatientObsLabel}>
|
|
48
61
|
{labelToDisplay ? t('labelColon', '{{label}}:', { label: labelToDisplay }) : ''}
|
|
49
62
|
</span>
|
|
50
|
-
{obsNodes}
|
|
63
|
+
<div className={styles.dotSeparatedChildren}>{obsNodes}</div>
|
|
51
64
|
</div>
|
|
52
65
|
);
|
|
53
66
|
} else {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { ChemistryReference } from '@carbon/react/icons';
|
|
3
|
+
import styles from '../ward-patient-card.scss';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { type WardPatient } from '../../types';
|
|
6
|
+
import { usePatientPendingOrders } from '../../hooks/usePatientPendingOrders';
|
|
7
|
+
|
|
8
|
+
export interface WardPatientPendingOrderProps {
|
|
9
|
+
wardPatient: WardPatient;
|
|
10
|
+
orderUuid: string;
|
|
11
|
+
label: string;
|
|
12
|
+
onOrderCount: (count: number) => void; // New prop for notifying parent
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const WardPatientPendingOrder: React.FC<WardPatientPendingOrderProps> = ({
|
|
16
|
+
wardPatient,
|
|
17
|
+
orderUuid,
|
|
18
|
+
label,
|
|
19
|
+
onOrderCount,
|
|
20
|
+
}) => {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const { count, isLoading } = usePatientPendingOrders(
|
|
23
|
+
wardPatient?.patient?.uuid,
|
|
24
|
+
orderUuid,
|
|
25
|
+
wardPatient?.visit?.startDatetime.split('T')[0],
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!isLoading) {
|
|
30
|
+
onOrderCount(count); // Notify parent when count is available
|
|
31
|
+
}
|
|
32
|
+
}, [count, isLoading, onOrderCount]);
|
|
33
|
+
|
|
34
|
+
if (isLoading || !count || count == 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const labelToDisplay = label ? t(label) : t('Orders', 'Orders');
|
|
39
|
+
return (
|
|
40
|
+
<div className={styles.wardPatientCardDispositionTypeContainer}>
|
|
41
|
+
<ChemistryReference className={styles.chemistryReferenceIcon} size="24" />
|
|
42
|
+
{count} {labelToDisplay}
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Movement } from '@carbon/react/icons';
|
|
3
|
+
import styles from '../ward-patient-card.scss';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { type WardPatient } from '../../types';
|
|
6
|
+
|
|
7
|
+
export interface WardPatientTransferProps {
|
|
8
|
+
wardPatient: WardPatient;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const WardPatientPendingTransfer: React.FC<WardPatientTransferProps> = ({ wardPatient }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
|
|
14
|
+
const { dispositionType, dispositionLocation } = wardPatient?.inpatientRequest;
|
|
15
|
+
const message = useMemo(() => {
|
|
16
|
+
if (dispositionType === 'TRANSFER') {
|
|
17
|
+
if (dispositionLocation) {
|
|
18
|
+
return t('transferToDispositionLocation', 'Transfer to {{location}}', { location: dispositionLocation.name });
|
|
19
|
+
}
|
|
20
|
+
return t('pendingTransfer', 'Pending Transfer');
|
|
21
|
+
}
|
|
22
|
+
if (dispositionType === 'DISCHARGE') {
|
|
23
|
+
return t('pendingDischarge', 'Pending Discharge');
|
|
24
|
+
}
|
|
25
|
+
return '';
|
|
26
|
+
}, [dispositionType, dispositionLocation]);
|
|
27
|
+
|
|
28
|
+
if (!(dispositionType === 'TRANSFER' || dispositionType === 'DISCHARGE')) return null;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className={styles.wardPatientCardDispositionTypeContainer}>
|
|
32
|
+
<Movement className={styles.movementIcon} size="24" />
|
|
33
|
+
{message}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default WardPatientPendingTransfer;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Toggletip, ToggletipButton, ToggletipContent, Tooltip } from '@carbon/react';
|
|
2
|
+
import { isDesktop, useLayoutType } from '@openmrs/esm-framework';
|
|
3
|
+
import React, { type ReactNode } from 'react';
|
|
4
|
+
import styles from '../ward-patient-card.scss';
|
|
5
|
+
|
|
6
|
+
interface WardPatientResponsiveTooltipProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
tooltipContent: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This component acts as a Tooltip on desktop and a Toggletip on mobile.
|
|
13
|
+
*/
|
|
14
|
+
const WardPatientResponsiveTooltip: React.FC<WardPatientResponsiveTooltipProps> = ({ children, tooltipContent }) => {
|
|
15
|
+
const layout = useLayoutType();
|
|
16
|
+
if (isDesktop(layout)) {
|
|
17
|
+
return (
|
|
18
|
+
<Tooltip description={tooltipContent} className={styles.responsiveTooltip}>
|
|
19
|
+
{children}
|
|
20
|
+
</Tooltip>
|
|
21
|
+
);
|
|
22
|
+
} else {
|
|
23
|
+
return (
|
|
24
|
+
<Toggletip className={styles.responsiveTooltip}>
|
|
25
|
+
<ToggletipButton>{children}</ToggletipButton>
|
|
26
|
+
<ToggletipContent>{tooltipContent}</ToggletipContent>
|
|
27
|
+
</Toggletip>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default WardPatientResponsiveTooltip;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SkeletonIcon } from '@carbon/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styles from '../ward-patient-card.scss';
|
|
4
|
+
|
|
5
|
+
const WardPatientSkeletonText = () => {
|
|
6
|
+
return <SkeletonIcon className={styles.skeletonText} />;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default WardPatientSkeletonText;
|