@kenyaemr/esm-ward-app 8.1.1-pre.114 → 8.1.1-pre.118
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 +18 -17
- package/dist/109.js +1 -0
- package/dist/109.js.map +1 -0
- package/dist/125.js +1 -0
- package/dist/125.js.map +1 -0
- package/dist/126.js +1 -0
- package/dist/126.js.map +1 -0
- package/dist/130.js +1 -1
- 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/161.js +2 -0
- package/dist/161.js.map +1 -0
- package/dist/269.js +1 -1
- package/dist/269.js.map +1 -1
- package/dist/466.js +1 -1
- package/dist/466.js.map +1 -1
- package/dist/500.js +1 -0
- package/dist/500.js.map +1 -0
- package/dist/53.js +1 -0
- package/dist/53.js.map +1 -0
- package/dist/557.js +1 -0
- package/dist/557.js.map +1 -0
- package/dist/559.js +1 -0
- package/dist/559.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/577.js +1 -1
- package/dist/577.js.map +1 -1
- package/dist/659.js +1 -1
- package/dist/659.js.map +1 -1
- package/dist/701.js +1 -0
- package/dist/701.js.map +1 -0
- package/dist/749.js +1 -1
- package/dist/749.js.map +1 -1
- package/dist/908.js +1 -0
- package/dist/908.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 +294 -74
- package/dist/kenyaemr-esm-ward-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/mock.tsx +54 -0
- package/package.json +1 -1
- 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 +2 -1
- package/src/beds/empty-bed.scss +0 -4
- package/src/beds/occupied-bed.scss +1 -0
- package/src/config-schema-mother-child-row.ts +26 -0
- package/src/config-schema-pending-items-extension.ts +29 -0
- package/src/config-schema.ts +12 -14
- package/src/hooks/useAdmissionLocation.ts +22 -4
- 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 +2 -6
- package/src/hooks/usePatientPendingOrders.ts +16 -0
- package/src/hooks/useWardPatientGrouping.ts +25 -0
- package/src/index.ts +50 -3
- package/src/location-selector/location-selector.component.tsx +18 -21
- package/src/routes.json +43 -0
- package/src/types/index.ts +34 -0
- package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +7 -2
- package/src/ward-patient-card/card-rows/mother-child-row.extension.tsx +110 -0
- package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
- package/src/ward-patient-card/card-rows/pending-items-car-row.extension.tsx +50 -0
- package/src/ward-patient-card/row-elements/ward-pateint-skeleton-text.tsx +9 -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 +54 -36
- package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +2 -3
- 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 +36 -32
- package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +15 -9
- 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/ward-patient-card-element.component.tsx +4 -0
- package/src/ward-patient-card/ward-patient-card.component.tsx +21 -14
- package/src/ward-patient-card/ward-patient-card.scss +61 -8
- package/src/ward-patient-card/ward-patient-resource.ts +15 -0
- package/src/ward-view/ward-view.component.tsx +124 -132
- package/src/ward-view/ward-view.resource.ts +121 -1
- package/src/ward-view/ward-view.scss +16 -6
- package/src/ward-view/ward-view.test.tsx +27 -42
- package/src/ward-view-header/admission-requests-bar.component.tsx +8 -7
- package/src/ward-view-header/admission-requests-bar.test.tsx +8 -21
- 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 +77 -0
- package/src/ward-view-header/ward-metrics.scss +8 -0
- package/src/ward-view-header/ward-metrics.test.tsx +91 -0
- package/src/ward-view-header/ward-view-header.component.tsx +3 -0
- package/src/ward-view-header/ward-view-header.scss +0 -1
- package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +11 -3
- package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +4 -5
- package/src/ward-workspace/admission-request-card/admission-request-card.scss +8 -4
- package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +8 -3
- package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +2 -0
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +29 -61
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +37 -21
- package/src/ward-workspace/patient-banner/patient-banner.component.tsx +3 -3
- 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 +7 -5
- package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
- package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +120 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +40 -30
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +29 -22
- 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 +2 -2
- 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 +6 -0
- package/translations/en.json +18 -1
- package/dist/346.js +0 -1
- package/dist/346.js.map +0 -1
- package/dist/76.js +0 -1
- package/dist/76.js.map +0 -1
- package/dist/803.js +0 -1
- package/dist/803.js.map +0 -1
- package/dist/958.js +0 -2
- package/dist/958.js.map +0 -1
- package/dist/960.js +0 -1
- package/dist/960.js.map +0 -1
- /package/dist/{958.js.LICENSE.txt → 161.js.LICENSE.txt} +0 -0
- /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
package/src/index.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { coloredObsTagsCardRowConfigSchema } from './config-schema-extension-col
|
|
|
12
12
|
import { moduleName } from './constant';
|
|
13
13
|
import { createDashboardLink } from './createDashboardLink.component';
|
|
14
14
|
import rootComponent from './root.component';
|
|
15
|
+
import { motherChildRowConfigSchema } from './config-schema-mother-child-row';
|
|
16
|
+
import { pendingItemsExtensionConfigSchema } from './config-schema-pending-items-extension';
|
|
15
17
|
|
|
16
18
|
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
17
19
|
|
|
@@ -38,7 +40,7 @@ export const admitPatientFormWorkspace = getAsyncLifecycle(
|
|
|
38
40
|
|
|
39
41
|
// Title for this workspace is set dynamically
|
|
40
42
|
export const wardPatientWorkspace = getAsyncLifecycle(
|
|
41
|
-
() => import('./ward-patient-
|
|
43
|
+
() => import('./ward-workspace/patient-details/ward-patient.workspace'),
|
|
42
44
|
options,
|
|
43
45
|
);
|
|
44
46
|
|
|
@@ -49,7 +51,7 @@ export const wardPatientNotesWorkspace = getAsyncLifecycle(
|
|
|
49
51
|
);
|
|
50
52
|
|
|
51
53
|
export const wardPatientActionButtonExtension = getAsyncLifecycle(
|
|
52
|
-
() => import('./ward-patient-
|
|
54
|
+
() => import('./ward-workspace/patient-details/ward-patient-action-button.extension'),
|
|
53
55
|
options,
|
|
54
56
|
);
|
|
55
57
|
|
|
@@ -68,26 +70,71 @@ export const admissionRequestNoteRowExtension = getAsyncLifecycle(
|
|
|
68
70
|
options,
|
|
69
71
|
);
|
|
70
72
|
|
|
73
|
+
export const motherChildRowExtension = getAsyncLifecycle(
|
|
74
|
+
() => import('./ward-patient-card/card-rows/mother-child-row.extension'),
|
|
75
|
+
options,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export const pendingItemsCardRowExtension = getAsyncLifecycle(
|
|
79
|
+
() => import('./ward-patient-card/card-rows/pending-items-car-row.extension'),
|
|
80
|
+
options,
|
|
81
|
+
);
|
|
82
|
+
|
|
71
83
|
// t('transfers', 'Transfers')
|
|
72
84
|
export const patientTransferAndSwapWorkspace = getAsyncLifecycle(
|
|
73
85
|
() => import('./ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace'),
|
|
74
86
|
options,
|
|
75
87
|
);
|
|
76
88
|
|
|
89
|
+
// t('discharge', 'Discharge')
|
|
90
|
+
export const patientDischargeWorkspace = getAsyncLifecycle(
|
|
91
|
+
() => import('./ward-workspace/patient-discharge/patient-discharge.workspace'),
|
|
92
|
+
options,
|
|
93
|
+
);
|
|
94
|
+
|
|
77
95
|
export const patientTransferAndSwapWorkspaceSiderailIcon = getAsyncLifecycle(
|
|
78
96
|
() => import('./action-menu-buttons/transfer-workspace-siderail.component'),
|
|
79
97
|
options,
|
|
80
98
|
);
|
|
81
99
|
|
|
100
|
+
// t('transferRequest', 'Transfer request')
|
|
101
|
+
export const patientTransferRequestWorkspace = getAsyncLifecycle(
|
|
102
|
+
() => import('./ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace'),
|
|
103
|
+
options,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export const patientDischargeWorkspaceSideRailIcon = getAsyncLifecycle(
|
|
107
|
+
() => import('./action-menu-buttons/discharge-workspace-siderail.component'),
|
|
108
|
+
options,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
export const patientClinicalFormsWorkspace = getAsyncLifecycle(
|
|
112
|
+
() => import('./ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace'),
|
|
113
|
+
options,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
export const clinicalFormWorkspaceSideRailIcon = getAsyncLifecycle(
|
|
117
|
+
() => import('./action-menu-buttons/clinical-forms-workspace-siderail.component'),
|
|
118
|
+
options,
|
|
119
|
+
);
|
|
120
|
+
|
|
82
121
|
export function startupApp() {
|
|
83
122
|
registerBreadcrumbs([]);
|
|
84
123
|
defineConfigSchema(moduleName, configSchema);
|
|
85
124
|
defineExtensionConfigSchema('colored-obs-tags-card-row', coloredObsTagsCardRowConfigSchema);
|
|
86
125
|
defineExtensionConfigSchema('admission-request-note-card-row', admissionRequestNoteRowConfigSchema);
|
|
126
|
+
defineExtensionConfigSchema('mother-child-card-row', motherChildRowConfigSchema);
|
|
127
|
+
defineExtensionConfigSchema('ward-patient-pending-items-card-row', pendingItemsExtensionConfigSchema);
|
|
87
128
|
|
|
88
129
|
registerFeatureFlag(
|
|
89
130
|
'bedmanagement-module',
|
|
90
|
-
'Bed
|
|
131
|
+
'Bed management module',
|
|
91
132
|
'Enables features related to bed management / assignment. Requires the backend bed management module to be installed.',
|
|
92
133
|
);
|
|
134
|
+
|
|
135
|
+
registerFeatureFlag(
|
|
136
|
+
'ward-view-vertical-tiling',
|
|
137
|
+
'Ward view vertical tiling',
|
|
138
|
+
'Enable tiling of bed cards vertically in the ward view.',
|
|
139
|
+
);
|
|
93
140
|
}
|
|
@@ -24,29 +24,26 @@ export default function LocationSelector(props: LocationSelectorProps) {
|
|
|
24
24
|
const isTablet = !isDesktop(useLayoutType());
|
|
25
25
|
const [searchTerm, setSearchTerm] = useState('');
|
|
26
26
|
const debouncedSearchTerm = useDebounce(searchTerm);
|
|
27
|
-
const [page, setPage] = useState(1);
|
|
28
27
|
const filterCriteria: Array<Array<string>> = useMemo(() => {
|
|
29
28
|
const criteria = [];
|
|
30
29
|
if (debouncedSearchTerm) {
|
|
31
30
|
criteria.push(['name:contains', debouncedSearchTerm]);
|
|
32
31
|
}
|
|
33
|
-
criteria.push(['_count', size.toString()]);
|
|
34
32
|
if (emrConfiguration) {
|
|
35
33
|
criteria.push(['_tag', emrConfiguration.supportsTransferLocationTag.name]);
|
|
36
34
|
}
|
|
37
|
-
if (page > 1) {
|
|
38
|
-
criteria.push(['_getpagesoffset', ((page - 1) * size).toString()]);
|
|
39
|
-
}
|
|
40
35
|
return criteria;
|
|
41
|
-
}, [debouncedSearchTerm,
|
|
42
|
-
const {
|
|
36
|
+
}, [debouncedSearchTerm, emrConfiguration]);
|
|
37
|
+
const {
|
|
38
|
+
data: locations,
|
|
39
|
+
isLoading,
|
|
40
|
+
totalCount,
|
|
41
|
+
currentPage,
|
|
42
|
+
totalPages,
|
|
43
|
+
goToNext,
|
|
44
|
+
goToPrevious,
|
|
45
|
+
} = useLocations(filterCriteria, size, !emrConfiguration);
|
|
43
46
|
|
|
44
|
-
const handlePageChange = useCallback(
|
|
45
|
-
({ page: newPage }) => {
|
|
46
|
-
setPage(newPage);
|
|
47
|
-
},
|
|
48
|
-
[setPage, page],
|
|
49
|
-
);
|
|
50
47
|
const handleSearch = useCallback(
|
|
51
48
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
52
49
|
setSearchTerm(event.target.value);
|
|
@@ -84,30 +81,30 @@ export default function LocationSelector(props: LocationSelectorProps) {
|
|
|
84
81
|
</RadioButtonGroup>
|
|
85
82
|
</ResponsiveWrapper>
|
|
86
83
|
)}
|
|
87
|
-
{
|
|
84
|
+
{totalCount > size && (
|
|
88
85
|
<div className={styles.pagination}>
|
|
89
86
|
<span className={styles.bodyShort01}>
|
|
90
87
|
{t('showingLocations', '{{start}}-{{end}} of {{count}} locations', {
|
|
91
|
-
start: (
|
|
92
|
-
end: Math.min(
|
|
93
|
-
count:
|
|
88
|
+
start: (currentPage - 1) * size + 1,
|
|
89
|
+
end: Math.min(currentPage * size, totalCount),
|
|
90
|
+
count: totalCount,
|
|
94
91
|
})}
|
|
95
92
|
</span>
|
|
96
93
|
<div>
|
|
97
94
|
<IconButton
|
|
98
95
|
className={classNames(styles.button, styles.buttonLeft)}
|
|
99
|
-
disabled={
|
|
96
|
+
disabled={currentPage === 1}
|
|
100
97
|
kind="ghost"
|
|
101
98
|
label={t('previousPage', 'Previous page')}
|
|
102
|
-
onClick={() =>
|
|
99
|
+
onClick={() => goToPrevious()}>
|
|
103
100
|
<ChevronLeftIcon />
|
|
104
101
|
</IconButton>
|
|
105
102
|
<IconButton
|
|
106
103
|
className={styles.button}
|
|
107
|
-
disabled={
|
|
104
|
+
disabled={currentPage >= totalPages}
|
|
108
105
|
kind="ghost"
|
|
109
106
|
label={t('nextPage', 'Next page')}
|
|
110
|
-
onClick={() =>
|
|
107
|
+
onClick={() => goToNext()}>
|
|
111
108
|
<ChevronRightIcon />
|
|
112
109
|
</IconButton>
|
|
113
110
|
</div>
|
package/src/routes.json
CHANGED
|
@@ -50,10 +50,30 @@
|
|
|
50
50
|
"slot": "action-menu-ward-patient-items-slot",
|
|
51
51
|
"component": "patientTransferAndSwapWorkspaceSiderailIcon"
|
|
52
52
|
},
|
|
53
|
+
{
|
|
54
|
+
"name": "patient-discharge-siderail-button",
|
|
55
|
+
"slot": "action-menu-ward-patient-items-slot",
|
|
56
|
+
"component": "patientDischargeWorkspaceSideRailIcon"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "clinical-forms-workspace-siderail-button",
|
|
60
|
+
"component": "clinicalFormWorkspaceSideRailIcon",
|
|
61
|
+
"slot": "action-menu-ward-patient-items-slot"
|
|
62
|
+
},
|
|
53
63
|
{
|
|
54
64
|
"component": "admissionRequestNoteRowExtension",
|
|
55
65
|
"name": "admission-request-note-card-row",
|
|
56
66
|
"slot": "ward-patient-card-slot"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"component": "motherChildRowExtension",
|
|
70
|
+
"name": "mother-child-card-row",
|
|
71
|
+
"slot": "ward-patient-card-slot"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"component": "pendingItemsCardRowExtension",
|
|
75
|
+
"name": "ward-patient-pending-items-card-row",
|
|
76
|
+
"slot": "ward-patient-card-pending-items-slot"
|
|
57
77
|
}
|
|
58
78
|
],
|
|
59
79
|
"workspaces": [
|
|
@@ -93,6 +113,29 @@
|
|
|
93
113
|
"type": "transfer-swap-bed-form",
|
|
94
114
|
"hasOwnSidebar": true,
|
|
95
115
|
"sidebarFamily": "ward-patient"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "patient-transfer-request-workspace",
|
|
119
|
+
"component": "patientTransferRequestWorkspace",
|
|
120
|
+
"title": "transferRequest",
|
|
121
|
+
"type": "transfer-request-form"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "patient-discharge-workspace",
|
|
125
|
+
"component": "patientDischargeWorkspace",
|
|
126
|
+
"title": "discharge",
|
|
127
|
+
"type": "ward-patient-discharge",
|
|
128
|
+
"hasOwnSidebar": true,
|
|
129
|
+
"sidebarFamily": "ward-patient"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "ward-patient-clinical-forms-workspace",
|
|
133
|
+
"component": "patientClinicalFormsWorkspace",
|
|
134
|
+
"title": "clinicalForms",
|
|
135
|
+
"type": "ward-patient-clinical-forms",
|
|
136
|
+
"hasOwnSidebar": true,
|
|
137
|
+
"sidebarFamily": "ward-patient",
|
|
138
|
+
"width": "wider"
|
|
96
139
|
}
|
|
97
140
|
]
|
|
98
141
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
Visit,
|
|
10
10
|
} from '@openmrs/esm-framework';
|
|
11
11
|
import type React from 'react';
|
|
12
|
+
import type { useWardPatientGrouping } from '../hooks/useWardPatientGrouping';
|
|
12
13
|
|
|
13
14
|
export type WardPatientCard = React.FC<WardPatient>;
|
|
14
15
|
|
|
@@ -40,9 +41,14 @@ export type WardPatient = {
|
|
|
40
41
|
*/
|
|
41
42
|
inpatientRequest: InpatientRequest;
|
|
42
43
|
};
|
|
44
|
+
|
|
43
45
|
export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps {
|
|
44
46
|
wardPatient: WardPatient;
|
|
45
47
|
}
|
|
48
|
+
export interface MotherAndChildrenRelationships {
|
|
49
|
+
motherByChildUuid: Map<string, Patient>;
|
|
50
|
+
childrenByMotherUuid: Map<string, Array<Patient>>;
|
|
51
|
+
}
|
|
46
52
|
|
|
47
53
|
// server-side types defined in openmrs-module-bedmanagement:
|
|
48
54
|
|
|
@@ -134,6 +140,19 @@ export interface InpatientAdmission {
|
|
|
134
140
|
// the first encounter of the visit that is of encounterType "Admission" or "Transfer",
|
|
135
141
|
// regardless of the admission location
|
|
136
142
|
firstAdmissionOrTransferEncounter: Encounter;
|
|
143
|
+
|
|
144
|
+
// the current in patient request
|
|
145
|
+
currentInpatientRequest: InpatientRequest;
|
|
146
|
+
}
|
|
147
|
+
export interface WardAppContext {
|
|
148
|
+
allPatientsByPatientUuid: Map<string, Patient>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface MotherAndChild {
|
|
152
|
+
mother: Patient;
|
|
153
|
+
child: Patient;
|
|
154
|
+
motherAdmission: InpatientAdmission;
|
|
155
|
+
childAdmission: InpatientAdmission;
|
|
137
156
|
}
|
|
138
157
|
|
|
139
158
|
// TODO: Move these types to esm-core
|
|
@@ -185,6 +204,12 @@ export interface EncounterRole extends OpenmrsResourceStrict {
|
|
|
185
204
|
retired?: boolean;
|
|
186
205
|
}
|
|
187
206
|
|
|
207
|
+
export interface WardMetrics {
|
|
208
|
+
patients: string;
|
|
209
|
+
freeBeds: string;
|
|
210
|
+
capacity: string;
|
|
211
|
+
}
|
|
212
|
+
|
|
188
213
|
export interface EncounterPayload {
|
|
189
214
|
encounterDatetime?: string;
|
|
190
215
|
encounterType: string;
|
|
@@ -202,3 +227,12 @@ export interface ObsPayload {
|
|
|
202
227
|
value?: string;
|
|
203
228
|
groupMembers?: Array<ObsPayload>;
|
|
204
229
|
}
|
|
230
|
+
|
|
231
|
+
export interface MotherAndChildren {
|
|
232
|
+
childAdmission: InpatientAdmission;
|
|
233
|
+
child: Patient;
|
|
234
|
+
motherAdmission: InpatientAdmission;
|
|
235
|
+
mother: Patient;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export type WardPatientGroupDetails = ReturnType<typeof useWardPatientGrouping>;
|
|
@@ -3,6 +3,7 @@ import { type ObsElementDefinition } from '../../config-schema';
|
|
|
3
3
|
import { type WardPatientCard } from '../../types';
|
|
4
4
|
import WardPatientObs from '../row-elements/ward-patient-obs';
|
|
5
5
|
import { useConfig } from '@openmrs/esm-framework';
|
|
6
|
+
import styles from '../ward-patient-card.scss';
|
|
6
7
|
|
|
7
8
|
const AdmissionRequestNoteRowExtension: WardPatientCard = ({ patient, visit, inpatientAdmission }) => {
|
|
8
9
|
const { conceptUuid } = useConfig<ObsElementDefinition>();
|
|
@@ -18,9 +19,13 @@ const AdmissionRequestNoteRowExtension: WardPatientCard = ({ patient, visit, inp
|
|
|
18
19
|
// only show if the patient has not been admitted yet
|
|
19
20
|
const admitted = inpatientAdmission != null;
|
|
20
21
|
if (admitted) {
|
|
21
|
-
return
|
|
22
|
+
return null;
|
|
22
23
|
} else {
|
|
23
|
-
return
|
|
24
|
+
return (
|
|
25
|
+
<div className={styles.wardPatientCardRow}>
|
|
26
|
+
<WardPatientObs config={config} patient={patient} visit={visit} />
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
24
29
|
}
|
|
25
30
|
};
|
|
26
31
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { InlineNotification } from '@carbon/react';
|
|
2
|
+
import { BabyIcon, MotherIcon } from '@openmrs/esm-framework';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { type MothersAndChildrenSearchCriteria, useMotherAndChildren } from '../../hooks/useMotherAndChildren';
|
|
7
|
+
import { type WardPatientCard } from '../../types';
|
|
8
|
+
import WardPatientSkeletonText from '../row-elements/ward-pateint-skeleton-text';
|
|
9
|
+
import WardPatientAge from '../row-elements/ward-patient-age';
|
|
10
|
+
import WardPatientIdentifier from '../row-elements/ward-patient-identifier';
|
|
11
|
+
import WardPatientLocation from '../row-elements/ward-patient-location';
|
|
12
|
+
import WardPatientName from '../row-elements/ward-patient-name';
|
|
13
|
+
import wardPatientCardStyles from '../ward-patient-card.scss';
|
|
14
|
+
import styles from './mother-child-row.scss';
|
|
15
|
+
|
|
16
|
+
const motherAndChildrenRep =
|
|
17
|
+
'custom:(childAdmission,mother:(person,identifiers:full,uuid),child:(person,identifiers:full,uuid),motherAdmission)';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This extension displays the mother or children of the patient in the patient card.
|
|
21
|
+
*
|
|
22
|
+
* @param param0
|
|
23
|
+
* @returns
|
|
24
|
+
*/
|
|
25
|
+
const MotherChildRowExtension: WardPatientCard = ({ patient }) => {
|
|
26
|
+
const { t } = useTranslation();
|
|
27
|
+
|
|
28
|
+
const getChildrenRequestParams: MothersAndChildrenSearchCriteria = {
|
|
29
|
+
mothers: [patient.uuid],
|
|
30
|
+
requireMotherHasActiveVisit: true,
|
|
31
|
+
requireChildHasActiveVisit: true,
|
|
32
|
+
requireChildBornDuringMothersActiveVisit: true,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getMotherRequestParams: MothersAndChildrenSearchCriteria = {
|
|
36
|
+
children: [patient.uuid],
|
|
37
|
+
requireMotherHasActiveVisit: true,
|
|
38
|
+
requireChildHasActiveVisit: true,
|
|
39
|
+
requireChildBornDuringMothersActiveVisit: true,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
data: childrenData,
|
|
44
|
+
isLoading: isLoadingChildrenData,
|
|
45
|
+
error: childrenDataError,
|
|
46
|
+
} = useMotherAndChildren(getChildrenRequestParams, true, motherAndChildrenRep);
|
|
47
|
+
const {
|
|
48
|
+
data: motherData,
|
|
49
|
+
isLoading: isLoadingMotherData,
|
|
50
|
+
error: motherDataError,
|
|
51
|
+
} = useMotherAndChildren(getMotherRequestParams, true, motherAndChildrenRep);
|
|
52
|
+
|
|
53
|
+
if (isLoadingChildrenData || isLoadingMotherData) {
|
|
54
|
+
return (
|
|
55
|
+
<div className={classNames(styles.motherOrBabyRow, wardPatientCardStyles.wardPatientCardRow)}>
|
|
56
|
+
<WardPatientSkeletonText />
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
} else if (childrenDataError) {
|
|
60
|
+
return (
|
|
61
|
+
<InlineNotification
|
|
62
|
+
kind="warning"
|
|
63
|
+
lowContrast={true}
|
|
64
|
+
title={t('errorLoadingChildren', 'Error loading children info')}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
} else if (motherDataError) {
|
|
68
|
+
return (
|
|
69
|
+
<InlineNotification
|
|
70
|
+
kind="warning"
|
|
71
|
+
lowContrast={true}
|
|
72
|
+
title={t('errorLoadingChildren', 'Error loading mother info')}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
{[...childrenData, ...motherData]?.map(({ mother, motherAdmission, child, childAdmission }) => {
|
|
80
|
+
// patient A is the patient card's patient
|
|
81
|
+
const patientA = patient;
|
|
82
|
+
// patient B is either the mother or the child of patient A
|
|
83
|
+
const isPatientBTheMother = mother.uuid != patientA.uuid;
|
|
84
|
+
const patientB = isPatientBTheMother ? mother : child;
|
|
85
|
+
|
|
86
|
+
// we display patient B here
|
|
87
|
+
const Icon = isPatientBTheMother ? MotherIcon : BabyIcon;
|
|
88
|
+
const patientBAdmission = isPatientBTheMother ? motherAdmission : childAdmission;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
key={patientB.uuid}
|
|
93
|
+
className={classNames(styles.motherOrBabyRow, wardPatientCardStyles.wardPatientCardRow)}>
|
|
94
|
+
<div className={styles.motherOrBabyIconDiv}>
|
|
95
|
+
<Icon className={styles.motherOrBabyIcon} size={24} />
|
|
96
|
+
</div>
|
|
97
|
+
<div className={classNames(styles.motherOrBabyRowElementsDiv, wardPatientCardStyles.dotSeparatedChildren)}>
|
|
98
|
+
<WardPatientName patient={patientB} />
|
|
99
|
+
<WardPatientIdentifier patient={patientB} />
|
|
100
|
+
<WardPatientAge patient={patientB} />
|
|
101
|
+
<WardPatientLocation inpatientAdmission={patientBAdmission} />
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
})}
|
|
106
|
+
</>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default MotherChildRowExtension;
|
|
@@ -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,50 @@
|
|
|
1
|
+
import React, { useCallback, useEffect } from 'react';
|
|
2
|
+
import { type WardPatientCard } from '../../types';
|
|
3
|
+
import { Hourglass } from '@carbon/react/icons';
|
|
4
|
+
|
|
5
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
6
|
+
import type { PendingItemsDefinition } from '../../config-schema';
|
|
7
|
+
import { WardPatientPendingOrder } from '../row-elements/ward-patient-pending-order.component';
|
|
8
|
+
import styles from '../ward-patient-card.scss';
|
|
9
|
+
import WardPatientPendingTransfer from '../row-elements/ward-patient-pending-transfer';
|
|
10
|
+
|
|
11
|
+
const PendingItemsCarRowExtension: WardPatientCard = (wardPatient) => {
|
|
12
|
+
const { orders, showPendingItems } = useConfig<PendingItemsDefinition>();
|
|
13
|
+
const [hasPendingOrders, setHasPendingOrders] = React.useState(false);
|
|
14
|
+
|
|
15
|
+
const hasPendingItems = !!wardPatient?.inpatientRequest || hasPendingOrders;
|
|
16
|
+
|
|
17
|
+
const handlePendingOrderCount = useCallback((count: number) => {
|
|
18
|
+
if (count > 0) {
|
|
19
|
+
setHasPendingOrders(true);
|
|
20
|
+
}
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!orders?.orderTypes?.length) {
|
|
25
|
+
setHasPendingOrders(false);
|
|
26
|
+
}
|
|
27
|
+
}, [orders]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={styles.wardPatientCardPendingItemsRow}>
|
|
31
|
+
{showPendingItems && hasPendingItems ? (
|
|
32
|
+
<>
|
|
33
|
+
<Hourglass className={styles.hourGlassIcon} size="16" />:
|
|
34
|
+
</>
|
|
35
|
+
) : null}
|
|
36
|
+
{orders?.orderTypes.map(({ uuid, label }) => (
|
|
37
|
+
<WardPatientPendingOrder
|
|
38
|
+
key={`pending-order-type-${uuid}`}
|
|
39
|
+
wardPatient={wardPatient}
|
|
40
|
+
orderUuid={uuid}
|
|
41
|
+
label={label}
|
|
42
|
+
onOrderCount={handlePendingOrderCount}
|
|
43
|
+
/>
|
|
44
|
+
))}
|
|
45
|
+
{wardPatient?.inpatientRequest ? <WardPatientPendingTransfer wardPatient={wardPatient} /> : null}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default PendingItemsCarRowExtension;
|
|
@@ -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;
|
|
@@ -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,12 +1,13 @@
|
|
|
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 {
|
|
5
|
+
import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags';
|
|
6
6
|
import { useObs } from '../../hooks/useObs';
|
|
7
7
|
import styles from '../ward-patient-card.scss';
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
8
|
+
import WardPatientSkeletonText from './ward-pateint-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
|
config: ColoredObsTagsCardRowConfigObject;
|
|
@@ -25,49 +26,66 @@ interface WardPatientCodedObsTagsProps {
|
|
|
25
26
|
* @returns
|
|
26
27
|
*/
|
|
27
28
|
const WardPatientCodedObsTags: React.FC<WardPatientCodedObsTagsProps> = ({ config, patient, visit }) => {
|
|
28
|
-
const { conceptUuid, summaryLabel, summaryLabelColor
|
|
29
|
+
const { conceptUuid, summaryLabel, summaryLabelColor } = config;
|
|
29
30
|
const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation);
|
|
30
31
|
const { t } = useTranslation();
|
|
31
|
-
const
|
|
32
|
+
const conceptToTagColorMap = useConceptToTagColorMap(config.tags);
|
|
32
33
|
|
|
33
34
|
if (isLoading) {
|
|
34
|
-
return
|
|
35
|
+
return (
|
|
36
|
+
<div className={styles.wardPatientCardRow}>
|
|
37
|
+
<WardPatientSkeletonText />
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
35
40
|
} else {
|
|
36
|
-
const obsToDisplay = data?.
|
|
41
|
+
const obsToDisplay = data?.filter((o) => {
|
|
37
42
|
const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
|
|
38
|
-
return matchVisit
|
|
43
|
+
return matchVisit;
|
|
39
44
|
});
|
|
40
45
|
|
|
41
|
-
const summaryLabelToDisplay =
|
|
42
|
-
summaryLabel != null
|
|
43
|
-
? translateFrom(summaryLabelI18nModule ?? moduleName, summaryLabel)
|
|
44
|
-
: obsToDisplay?.[0]?.concept?.display;
|
|
46
|
+
const summaryLabelToDisplay = summaryLabel != null ? t(summaryLabel) : obsToDisplay?.[0]?.concept?.display;
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
// for each obs configured to be displayed with a color, we create a tag for it
|
|
49
|
+
// for other obs not configured, we create a single summary tag for all of them.
|
|
50
|
+
const summaryTagTooltipText: ReactNode[] = [];
|
|
51
|
+
const coloredOpsTags = obsToDisplay
|
|
52
|
+
?.map((o) => {
|
|
53
|
+
const { display, uuid } = o.value as OpenmrsResource;
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const color = conceptToTagColorMap?.get(uuid);
|
|
56
|
+
if (color) {
|
|
57
|
+
return (
|
|
58
|
+
<WardPatientResponsiveTooltip tooltipContent={getObsEncounterString(o, t)}>
|
|
59
|
+
<Tag type={color} key={`ward-coded-obs-tag-${o.uuid}`}>
|
|
60
|
+
{display}
|
|
61
|
+
</Tag>
|
|
62
|
+
</WardPatientResponsiveTooltip>
|
|
63
|
+
);
|
|
64
|
+
} else {
|
|
65
|
+
summaryTagTooltipText.push(
|
|
66
|
+
<div key={uuid}>
|
|
67
|
+
{display} ({getObsEncounterString(o, t)})
|
|
68
|
+
</div>,
|
|
69
|
+
);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
.filter((o) => o != null);
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
if (obsNodes?.length > 0 || obsWithNoTagCount > 0) {
|
|
75
|
+
if (coloredOpsTags?.length > 0 || summaryTagTooltipText.length > 0) {
|
|
63
76
|
return (
|
|
64
|
-
<div>
|
|
77
|
+
<div className={styles.wardPatientCardRow}>
|
|
65
78
|
<span className={styles.wardPatientObsLabel}>
|
|
66
|
-
{
|
|
67
|
-
{
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
{coloredOpsTags}
|
|
80
|
+
{summaryTagTooltipText.length > 0 ? (
|
|
81
|
+
<WardPatientResponsiveTooltip tooltipContent={summaryTagTooltipText}>
|
|
82
|
+
<Tag type={summaryLabelColor}>
|
|
83
|
+
{t('countItems', '{{count}} {{item}}', {
|
|
84
|
+
count: summaryTagTooltipText.length,
|
|
85
|
+
item: summaryLabelToDisplay,
|
|
86
|
+
})}
|
|
87
|
+
</Tag>
|
|
88
|
+
</WardPatientResponsiveTooltip>
|
|
71
89
|
) : null}
|
|
72
90
|
</span>
|
|
73
91
|
</div>
|
|
@@ -33,15 +33,14 @@ const defaultConfig: IdentifierElementDefinition = {
|
|
|
33
33
|
const WardPatientIdentifier: React.FC<WardPatientIdentifierProps> = ({ config: configProp, patient }) => {
|
|
34
34
|
const { t } = useTranslation();
|
|
35
35
|
const config = configProp ?? defaultConfig;
|
|
36
|
-
const { identifierTypeUuid,
|
|
36
|
+
const { identifierTypeUuid, label } = config;
|
|
37
37
|
const patientIdentifiers = patient.identifiers.filter(
|
|
38
38
|
(patientIdentifier: PatientIdentifier) =>
|
|
39
39
|
identifierTypeUuid == null || patientIdentifier.identifierType?.uuid === identifierTypeUuid,
|
|
40
40
|
);
|
|
41
41
|
patientIdentifiers.sort(identifierCompareFunction);
|
|
42
42
|
const patientIdentifier = patientIdentifiers[0];
|
|
43
|
-
const labelToDisplay =
|
|
44
|
-
label != null ? translateFrom(labelModule ?? moduleName, label) : patientIdentifier?.identifierType?.name;
|
|
43
|
+
const labelToDisplay = label != null ? t(label) : patientIdentifier?.identifierType?.name;
|
|
45
44
|
return (
|
|
46
45
|
<div>
|
|
47
46
|
{labelToDisplay ? <Tag>{t('identifierTypelabel', '{{label}}:', { label: labelToDisplay })}</Tag> : <></>}
|