@palladium-ethiopia/esm-clinical-workflow-app 5.4.2-pre.26 → 5.4.2-pre.31
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 +5 -5
- package/dist/152.js +1 -1
- package/dist/152.js.map +1 -1
- package/dist/159.js +1 -1
- package/dist/159.js.map +1 -1
- package/dist/160.js +1 -0
- package/dist/160.js.map +1 -0
- package/dist/164.js +1 -1
- package/dist/164.js.map +1 -1
- package/dist/208.js +1 -1
- package/dist/208.js.map +1 -1
- package/dist/209.js +1 -1
- package/dist/209.js.map +1 -1
- package/dist/313.js +1 -0
- package/dist/313.js.map +1 -0
- package/dist/330.js +1 -0
- package/dist/330.js.map +1 -0
- package/dist/363.js +1 -1
- package/dist/363.js.map +1 -1
- package/dist/416.js +1 -0
- package/dist/416.js.map +1 -0
- package/dist/442.js +1 -1
- package/dist/442.js.map +1 -1
- package/dist/466.js +1 -1
- package/dist/466.js.map +1 -1
- package/dist/485.js +1 -0
- package/dist/485.js.map +1 -0
- package/dist/534.js +1 -1
- package/dist/534.js.map +1 -1
- package/dist/61.js +1 -1
- package/dist/61.js.map +1 -1
- package/dist/677.js +1 -1
- package/dist/677.js.map +1 -1
- package/dist/689.js +1 -1
- package/dist/689.js.map +1 -1
- package/dist/697.js +1 -1
- package/dist/697.js.map +1 -1
- package/dist/712.js +1 -1
- package/dist/712.js.map +1 -1
- package/dist/771.js +1 -1
- package/dist/771.js.map +1 -1
- package/dist/789.js +1 -1
- package/dist/789.js.map +1 -1
- package/dist/914.js +9 -9
- package/dist/914.js.map +1 -1
- package/dist/926.js +2 -2
- package/dist/926.js.map +1 -1
- package/dist/ethiopia-esm-clinical-workflow-app.js +5 -5
- package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +144 -48
- package/dist/ethiopia-esm-clinical-workflow-app.js.map +1 -1
- package/dist/main.js +13 -13
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +21 -0
- package/src/index.ts +23 -0
- package/src/patient-chart/visit/visit-history-table/diagnosis-tags.module.scss +1 -1
- package/src/patient-notes/visit-notes-form.scss +8 -8
- package/src/patient-transfer/confirm-transfer-dialog.modal.tsx +58 -0
- package/src/patient-transfer/patient-transfer-action-button.extension.tsx +126 -0
- package/src/patient-transfer/queue-table-transfer-column.component.tsx +40 -0
- package/src/patient-transfer/transfer-data.resource.ts +66 -0
- package/src/patient-transfer/transfer-details.modal.tsx +42 -0
- package/src/patient-transfer/transfer-details.scss +18 -0
- package/src/patient-transfer/transfer-notes-overview.extension.tsx +63 -0
- package/src/patient-transfer/transfer-notes-overview.scss +88 -0
- package/src/patient-transfer/transfer-notes-table.component.tsx +205 -0
- package/src/patient-transfer/transfer-notes.resource.ts +93 -0
- package/src/routes.json +37 -0
- package/src/types/index.ts +15 -0
- package/translations/am.json +8 -0
- package/translations/en.json +8 -0
- package/dist/825.js +0 -1
- package/dist/825.js.map +0 -1
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[],"extensions":[{"name":"clinical-workflow-triage-dashboard-link","component":"triageDashboardLink","slot":"homepage-dashboard-slot","order":0,"meta":{"name":"clinical-workflow-triage","slot":"clinical-workflow-triage-dashboard-slot","title":"clinical-workflow-triage"}},{"name":"clinical-workflow-triage-dashboard","component":"triageDashboard","slot":"clinical-workflow-triage-dashboard-slot"},{"name":"ewf-mru-dashboard-link","component":"mruLeftPanelLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"mru","title":"MRU","path":"mru","slot":"mru-dashboard-slot"}},{"name":"ewf-mru-dashboard","component":"mruDashboard","slot":"mru-dashboard-slot","online":true,"offline":false},{"name":"patient-scoreboard-link","component":"patientScoreboardLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"patient-scoreboard","title":"Patient Scoreboard","path":"patient-scoreboard","slot":"patient-scoreboard-slot"}},{"name":"patient-scoreboard","component":"patientScoreboard","slot":"patient-scoreboard-slot","online":true,"offline":false},{"name":"past-visits-detail-overview-shadow","slot":"patient-chart-encounters-dashboard-slot","component":"pastVisitsDetailOverviewShadow","order":0,"meta":{"title":"Visits","view":"visits"},"online":true,"offline":true}],"workspaces":[{"name":"triage-workspace","component":"triageWorkspace","title":"Triage Form","canMaximize":true,"type":"clinical-form"},{"name":"patient-registration-workspace","component":"patientRegistrationWorkspace","title":"Patient Registration","canMaximize":true,"type":"registration-form"},{"name":"billing-information-workspace","component":"billingInformationWorkspace","title":"Billing Information Workspace","type":"clinical-form"}],"workspaces2":[{"name":"clinical-workflow-patient-form-entry-workspace","component":"@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace","window":"clinical-workflow-window"},{"name":"visit-notes-form-shadow-workspace","component":"visitNotesFormWorkspace","window":"visit-note-shadow"}],"workspaceWindows2":[{"name":"clinical-workflow-window","group":"clinical-workflow-group","width":"wider","canMaximize":true},{"name":"visit-note-shadow","group":"patient-chart","icon":"visitNoteActionButton","order":3}],"workspaceGroups2":[{"name":"clinical-workflow-group","overlay":true,"persistence":"closable"}],"version":"5.4.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[],"extensions":[{"name":"clinical-workflow-triage-dashboard-link","component":"triageDashboardLink","slot":"homepage-dashboard-slot","order":0,"meta":{"name":"clinical-workflow-triage","slot":"clinical-workflow-triage-dashboard-slot","title":"clinical-workflow-triage"}},{"name":"clinical-workflow-triage-dashboard","component":"triageDashboard","slot":"clinical-workflow-triage-dashboard-slot"},{"name":"ewf-mru-dashboard-link","component":"mruLeftPanelLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"mru","title":"MRU","path":"mru","slot":"mru-dashboard-slot"}},{"name":"ewf-mru-dashboard","component":"mruDashboard","slot":"mru-dashboard-slot","online":true,"offline":false},{"name":"patient-scoreboard-link","component":"patientScoreboardLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"patient-scoreboard","title":"Patient Scoreboard","path":"patient-scoreboard","slot":"patient-scoreboard-slot"}},{"name":"patient-scoreboard","component":"patientScoreboard","slot":"patient-scoreboard-slot","online":true,"offline":false},{"name":"past-visits-detail-overview-shadow","slot":"patient-chart-encounters-dashboard-slot","component":"pastVisitsDetailOverviewShadow","order":0,"meta":{"title":"Visits","view":"visits"},"online":true,"offline":true},{"name":"queue-table-transfer-column","component":"queueTableTransferColumn","slot":"queue-table-transfer-status-slot"},{"name":"transfer-notes-overview-widget","component":"transferNotesOverview","slot":"patient-chart-summary-dashboard-slot","meta":{"fullWidth":false},"order":6}],"workspaces":[{"name":"triage-workspace","component":"triageWorkspace","title":"Triage Form","canMaximize":true,"type":"clinical-form"},{"name":"patient-registration-workspace","component":"patientRegistrationWorkspace","title":"Patient Registration","canMaximize":true,"type":"registration-form"},{"name":"billing-information-workspace","component":"billingInformationWorkspace","title":"Billing Information Workspace","type":"clinical-form"}],"workspaces2":[{"name":"clinical-workflow-patient-form-entry-workspace","component":"@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace","window":"clinical-workflow-window"},{"name":"patient-transfer-form-entry-workspace","component":"@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace","window":"patient-transfer"},{"name":"visit-notes-form-shadow-workspace","component":"visitNotesFormWorkspace","window":"visit-note-shadow"}],"workspaceWindows2":[{"name":"clinical-workflow-window","group":"clinical-workflow-group","width":"wider","canMaximize":true},{"name":"visit-note-shadow","group":"patient-chart","icon":"visitNoteActionButton","order":3},{"name":"patient-transfer","group":"patient-chart","icon":"patientTransferActionButton","order":4,"width":"wider","canMaximize":true}],"workspaceGroups2":[{"name":"clinical-workflow-group","overlay":true,"persistence":"closable"}],"modals":[{"name":"confirm-transfer-dialog","component":"confirmTransferDialog"},{"name":"patient-transfer-details-modal","component":"patientTransferDetailsModal"}],"version":"5.4.2-pre.31"}
|
package/package.json
CHANGED
package/src/config-schema.ts
CHANGED
|
@@ -95,6 +95,21 @@ export const configSchema = {
|
|
|
95
95
|
_description: "UUID for the 'Drug' order type to fetch medications",
|
|
96
96
|
_default: '131168f4-15f5-102d-96e4-000c29c2a5d7',
|
|
97
97
|
},
|
|
98
|
+
patientTransferFormUuid: {
|
|
99
|
+
_type: Type.UUID,
|
|
100
|
+
_description: 'UUID of the patient transfer form',
|
|
101
|
+
_default: '94d5788f-6aaf-4ef6-b56e-1c71749cfa3e',
|
|
102
|
+
},
|
|
103
|
+
transferEncounterTypeUuid: {
|
|
104
|
+
_type: Type.UUID,
|
|
105
|
+
_description: 'UUID of the Intra-Hospital Transfer encounter type',
|
|
106
|
+
_default: '7b68d557-85ef-4fc8-b767-4fa4f5eb5c23',
|
|
107
|
+
},
|
|
108
|
+
transferNoteConceptUuid: {
|
|
109
|
+
_type: Type.UUID,
|
|
110
|
+
_description: 'UUID of the Transfer Note concept',
|
|
111
|
+
_default: 'f4162fe3-f7e3-4062-9bb3-aa1a4b1044a5',
|
|
112
|
+
},
|
|
98
113
|
};
|
|
99
114
|
|
|
100
115
|
export type ClinicalWorkflowConfig = {
|
|
@@ -115,6 +130,9 @@ export type ClinicalWorkflowConfig = {
|
|
|
115
130
|
identifierSourceUuid: string;
|
|
116
131
|
defaultIdentifierTypeUuid: string;
|
|
117
132
|
medicoLegalCasesAttributeTypeUuid: string;
|
|
133
|
+
patientTransferFormUuid: string;
|
|
134
|
+
transferEncounterTypeUuid: string;
|
|
135
|
+
transferNoteConceptUuid: string;
|
|
118
136
|
};
|
|
119
137
|
|
|
120
138
|
export interface VisitNoteConfig {
|
|
@@ -164,4 +182,7 @@ export interface ChartConfig {
|
|
|
164
182
|
}>;
|
|
165
183
|
otherConceptUuid: string;
|
|
166
184
|
drugOrderTypeUUID: string;
|
|
185
|
+
patientTransferFormUuid: string;
|
|
186
|
+
transferEncounterTypeUuid: string;
|
|
187
|
+
transferNoteConceptUuid: string;
|
|
167
188
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { spaBasePath } from './constants';
|
|
|
7
7
|
import BillingInformationWorkspace from './mru/billing-information/billing-information.workspace';
|
|
8
8
|
import PatientScoreboard from './patient-scoreboard/patient-scoreboard.component';
|
|
9
9
|
import visitNotesActionButtonExtension from './patient-notes/visit-note-action-button.extension';
|
|
10
|
+
import patientTransferActionButtonExtension from './patient-transfer/patient-transfer-action-button.extension';
|
|
10
11
|
import pastVisitsOverviewComponent from './patient-chart/visit/visits-widget/visit-detail-overview.component';
|
|
11
12
|
|
|
12
13
|
const moduleName = '@ethiopia/esm-clinical-workflow-app';
|
|
@@ -71,6 +72,13 @@ export const patientScoreboard = getSyncLifecycle(PatientScoreboard, options);
|
|
|
71
72
|
|
|
72
73
|
export const visitNoteActionButton = getSyncLifecycle(visitNotesActionButtonExtension, options);
|
|
73
74
|
|
|
75
|
+
export const patientTransferActionButton = getSyncLifecycle(patientTransferActionButtonExtension, options);
|
|
76
|
+
|
|
77
|
+
export const confirmTransferDialog = getAsyncLifecycle(
|
|
78
|
+
() => import('./patient-transfer/confirm-transfer-dialog.modal'),
|
|
79
|
+
options,
|
|
80
|
+
);
|
|
81
|
+
|
|
74
82
|
export const visitNotesFormWorkspace = getAsyncLifecycle(
|
|
75
83
|
() => import('./patient-notes/visit-notes-form-shadow.workspace'),
|
|
76
84
|
options,
|
|
@@ -80,3 +88,18 @@ export const pastVisitsDetailOverviewShadow = getSyncLifecycle(pastVisitsOvervie
|
|
|
80
88
|
featureName: 'visits-detail-overview',
|
|
81
89
|
moduleName,
|
|
82
90
|
});
|
|
91
|
+
|
|
92
|
+
export const queueTableTransferColumn = getAsyncLifecycle(
|
|
93
|
+
() => import('./patient-transfer/queue-table-transfer-column.component'),
|
|
94
|
+
options,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
export const patientTransferDetailsModal = getAsyncLifecycle(
|
|
98
|
+
() => import('./patient-transfer/transfer-details.modal'),
|
|
99
|
+
options,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
export const transferNotesOverview = getAsyncLifecycle(
|
|
103
|
+
() => import('./patient-transfer/transfer-notes-overview.extension'),
|
|
104
|
+
options,
|
|
105
|
+
);
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
.diagnosisList {
|
|
29
29
|
background-color: colors.$white;
|
|
30
|
-
max-height: layout.rem(320px);
|
|
30
|
+
max-height: layout.rem(320px);
|
|
31
31
|
overflow-y: auto;
|
|
32
32
|
border: 1px solid colors.$gray-20;
|
|
33
33
|
display: flex;
|
|
@@ -160,10 +160,10 @@
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
.diagnoserrorOutline {
|
|
163
|
-
max-width: layout.rem(320px);
|
|
163
|
+
max-width: layout.rem(320px);
|
|
164
164
|
|
|
165
165
|
:global(.cds--search-input):focus {
|
|
166
|
-
outline: 2px solid colors.$red-60;
|
|
166
|
+
outline: 2px solid colors.$red-60;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
:global(.cds--search-magnifier) {
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
border-radius: 2px;
|
|
266
266
|
transition: all 0.1s ease;
|
|
267
267
|
cursor: pointer;
|
|
268
|
-
min-height: layout.$spacing-08;
|
|
268
|
+
min-height: layout.$spacing-08;
|
|
269
269
|
|
|
270
270
|
&:hover {
|
|
271
271
|
background-color: colors.$gray-10;
|
|
@@ -287,11 +287,11 @@
|
|
|
287
287
|
min-width: layout.$spacing-07;
|
|
288
288
|
flex-shrink: 0;
|
|
289
289
|
padding: layout.$spacing-02 layout.$spacing-03;
|
|
290
|
-
height: layout.$spacing-07;
|
|
290
|
+
height: layout.$spacing-07;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
.certaintyDropdown {
|
|
294
|
-
position: relative;
|
|
294
|
+
position: relative;
|
|
295
295
|
z-index: 10;
|
|
296
296
|
margin-top: layout.$spacing-02;
|
|
297
297
|
background: colors.$white;
|
|
@@ -443,11 +443,11 @@
|
|
|
443
443
|
|
|
444
444
|
.diagnosisRow {
|
|
445
445
|
padding: layout.$spacing-03;
|
|
446
|
-
min-height: layout.$spacing-09;
|
|
446
|
+
min-height: layout.$spacing-09;
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
.certaintyOption {
|
|
450
450
|
padding: layout.$spacing-03;
|
|
451
451
|
min-height: layout.$spacing-09;
|
|
452
452
|
}
|
|
453
|
-
}
|
|
453
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
4
|
+
import { showModal, type Visit } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
interface ConfirmTransferDialogProps {
|
|
7
|
+
closeModal: () => void;
|
|
8
|
+
activeVisit: Visit;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ConfirmTransferDialog: React.FC<ConfirmTransferDialogProps> = ({ closeModal, activeVisit }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
14
|
+
|
|
15
|
+
const handleConfirm = () => {
|
|
16
|
+
if (isProcessing) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
setIsProcessing(true);
|
|
20
|
+
|
|
21
|
+
// 1. Initiate closing the current modal
|
|
22
|
+
closeModal();
|
|
23
|
+
|
|
24
|
+
// 2. The "Double Frame" Guarantee:
|
|
25
|
+
// First rAF: Wait for the close command to be acknowledged
|
|
26
|
+
window.requestAnimationFrame(() => {
|
|
27
|
+
// Second rAF: Wait for the browser to actually remove the element and repaint
|
|
28
|
+
window.requestAnimationFrame(() => {
|
|
29
|
+
const dispose = showModal('transition-patient-to-latest-queue-modal', {
|
|
30
|
+
activeVisit,
|
|
31
|
+
closeModal: () => {
|
|
32
|
+
setIsProcessing(false);
|
|
33
|
+
dispose();
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<ModalHeader closeModal={closeModal}>{t('transferFormSaved', 'Patient transfer form saved')}</ModalHeader>
|
|
43
|
+
<ModalBody>
|
|
44
|
+
<p>{t('moveToQueueQuestion', 'Do you want to move the patient to a queue now?')}</p>
|
|
45
|
+
</ModalBody>
|
|
46
|
+
<ModalFooter>
|
|
47
|
+
<Button kind="secondary" onClick={closeModal} size="lg">
|
|
48
|
+
{t('cancel', 'Cancel')}
|
|
49
|
+
</Button>
|
|
50
|
+
<Button autoFocus kind="primary" onClick={handleConfirm} size="lg" disabled={isProcessing}>
|
|
51
|
+
{t('yesMoveToQueue', 'Yes, move to queue')}
|
|
52
|
+
</Button>
|
|
53
|
+
</ModalFooter>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default ConfirmTransferDialog;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { type ComponentProps, useCallback } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { ArrowsHorizontal } from '@carbon/react/icons';
|
|
4
|
+
import { ActionMenuButton2, Encounter, launchWorkspace2, showModal, useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import {
|
|
6
|
+
useStartVisitIfNeeded,
|
|
7
|
+
type PatientChartWorkspaceActionButtonProps,
|
|
8
|
+
type PatientWorkspaceGroupProps,
|
|
9
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
10
|
+
import type { ClinicalWorkflowConfig } from '../config-schema';
|
|
11
|
+
|
|
12
|
+
const PatientTransferActionButton: React.FC<PatientChartWorkspaceActionButtonProps> = (props) => {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
const config = useConfig<ClinicalWorkflowConfig>();
|
|
15
|
+
|
|
16
|
+
const groupProps = props?.groupProps;
|
|
17
|
+
const patientUuid = groupProps?.patientUuid ?? '';
|
|
18
|
+
const patient = groupProps?.patient;
|
|
19
|
+
const visitContext = groupProps?.visitContext;
|
|
20
|
+
const mutateVisitContext = groupProps?.mutateVisitContext;
|
|
21
|
+
const transferFormUuid = config?.patientTransferFormUuid;
|
|
22
|
+
|
|
23
|
+
const startVisitIfNeeded = useStartVisitIfNeeded(patientUuid);
|
|
24
|
+
|
|
25
|
+
const handlePostResponse = useCallback(
|
|
26
|
+
(encounter: Encounter) => {
|
|
27
|
+
if (!visitContext) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Show confirmation dialog asking if physician wants to move patient to queue
|
|
32
|
+
const dispose = showModal('confirm-transfer-dialog', {
|
|
33
|
+
activeVisit: visitContext,
|
|
34
|
+
closeModal: () => dispose(),
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
[visitContext],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const handleLaunchWorkspace = useCallback(async () => {
|
|
41
|
+
const didStartVisit = await startVisitIfNeeded();
|
|
42
|
+
if (!didStartVisit) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!visitContext?.uuid || !visitContext?.visitType?.uuid) {
|
|
47
|
+
console.error('Invalid visit context: visit UUID or visit type UUID is missing');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!patient) {
|
|
52
|
+
console.error('Patient data is not available');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
launchWorkspace2<
|
|
57
|
+
{
|
|
58
|
+
form: { visitUuid: string; uuid: string; visitTypeUuid: string };
|
|
59
|
+
encounterUuid: string;
|
|
60
|
+
handlePostResponse?: (encounter: Encounter) => void;
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
patient: typeof patient;
|
|
64
|
+
patientUuid: string;
|
|
65
|
+
visitContext: typeof visitContext;
|
|
66
|
+
mutateVisitContext: typeof mutateVisitContext | null;
|
|
67
|
+
},
|
|
68
|
+
PatientWorkspaceGroupProps
|
|
69
|
+
>(
|
|
70
|
+
'patient-transfer-form-entry-workspace',
|
|
71
|
+
{
|
|
72
|
+
form: {
|
|
73
|
+
visitUuid: visitContext.uuid,
|
|
74
|
+
uuid: transferFormUuid ?? '',
|
|
75
|
+
visitTypeUuid: visitContext.visitType.uuid,
|
|
76
|
+
},
|
|
77
|
+
encounterUuid: '',
|
|
78
|
+
handlePostResponse,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
patient: patient!,
|
|
82
|
+
patientUuid,
|
|
83
|
+
visitContext,
|
|
84
|
+
mutateVisitContext: mutateVisitContext || null,
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
}, [
|
|
88
|
+
patientUuid,
|
|
89
|
+
patient,
|
|
90
|
+
visitContext,
|
|
91
|
+
mutateVisitContext,
|
|
92
|
+
transferFormUuid,
|
|
93
|
+
handlePostResponse,
|
|
94
|
+
startVisitIfNeeded,
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
if (!groupProps?.patientUuid || !patient) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<ActionMenuButton2
|
|
103
|
+
icon={(iconProps: ComponentProps<typeof ArrowsHorizontal>) => <ArrowsHorizontal {...iconProps} />}
|
|
104
|
+
label={t('transferPatient', 'Transfer Patient')}
|
|
105
|
+
workspaceToLaunch={{
|
|
106
|
+
workspaceName: 'patient-form-entry-workspace' as const,
|
|
107
|
+
workspaceProps: {
|
|
108
|
+
form: {
|
|
109
|
+
uuid: transferFormUuid ?? '',
|
|
110
|
+
display: t('transferPatient', 'Transfer Patient'),
|
|
111
|
+
name: 'Transfer Patient',
|
|
112
|
+
},
|
|
113
|
+
encounterUuid: '',
|
|
114
|
+
},
|
|
115
|
+
}}
|
|
116
|
+
onBeforeWorkspaceLaunch={async () => {
|
|
117
|
+
handleLaunchWorkspace().catch((error) => {
|
|
118
|
+
console.error('Error launching transfer form workspace:', error);
|
|
119
|
+
});
|
|
120
|
+
return false;
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default PatientTransferActionButton;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Tag } from '@carbon/react';
|
|
3
|
+
import { showModal } from '@openmrs/esm-framework';
|
|
4
|
+
import { useTransferData } from './transfer-data.resource';
|
|
5
|
+
import type { QueueEntry } from '../types';
|
|
6
|
+
|
|
7
|
+
interface QueueTableTransferColumnProps {
|
|
8
|
+
queueEntry: QueueEntry;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Queue table column component that displays transfer status
|
|
13
|
+
* and allows viewing transfer details in a modal
|
|
14
|
+
*/
|
|
15
|
+
const QueueTableTransferColumn: React.FC<QueueTableTransferColumnProps> = ({ queueEntry }) => {
|
|
16
|
+
const { transferData, isLoading } = useTransferData(queueEntry.visit?.uuid, queueEntry.patient?.uuid);
|
|
17
|
+
|
|
18
|
+
if (isLoading) {
|
|
19
|
+
return <span>...</span>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!transferData) {
|
|
23
|
+
return <Tag type="gray">No Transfer</Tag>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const handleViewTransfer = () => {
|
|
27
|
+
const dispose = showModal('patient-transfer-details-modal', {
|
|
28
|
+
transferData,
|
|
29
|
+
closeModal: () => dispose(),
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Button kind="ghost" size="sm" onClick={handleViewTransfer}>
|
|
35
|
+
View Transfer
|
|
36
|
+
</Button>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default QueueTableTransferColumn;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl, useConfig, type Encounter, type Obs } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import type { ClinicalWorkflowConfig } from '../config-schema';
|
|
5
|
+
|
|
6
|
+
export interface TransferData {
|
|
7
|
+
uuid: string;
|
|
8
|
+
encounterDatetime: string;
|
|
9
|
+
transferDate: string;
|
|
10
|
+
fromLocation: string;
|
|
11
|
+
note: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useTransferData(visitUuid: string, patientUuid: string) {
|
|
15
|
+
const config = useConfig<ClinicalWorkflowConfig>();
|
|
16
|
+
const { transferEncounterTypeUuid, transferNoteConceptUuid } = config;
|
|
17
|
+
const url = visitUuid
|
|
18
|
+
? `${restBaseUrl}/encounter?patient=${patientUuid}&visit=${visitUuid}&encounterType=${transferEncounterTypeUuid}&v=custom:(uuid,encounterDatetime,location:(uuid,display),obs:(uuid,concept:(uuid,display),value,obsDatetime))`
|
|
19
|
+
: null;
|
|
20
|
+
|
|
21
|
+
const { data, error, isLoading } = useSWR<{ data: { results: Encounter[] } }>(url, openmrsFetch);
|
|
22
|
+
|
|
23
|
+
const transferData = useMemo(() => {
|
|
24
|
+
if (!data?.data?.results?.length) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sortedResults = data.data.results.sort(
|
|
29
|
+
(a, b) => new Date(b.encounterDatetime).getTime() - new Date(a.encounterDatetime).getTime(),
|
|
30
|
+
);
|
|
31
|
+
const transferEncounter = sortedResults[0];
|
|
32
|
+
|
|
33
|
+
return parseTransferEncounter(transferEncounter, transferNoteConceptUuid);
|
|
34
|
+
}, [data, transferNoteConceptUuid]);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
transferData,
|
|
38
|
+
isLoading,
|
|
39
|
+
error,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseTransferEncounter(encounter: Encounter, transferNoteConceptUuid: string): TransferData {
|
|
44
|
+
const obs = encounter.obs || [];
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
uuid: encounter.uuid,
|
|
48
|
+
encounterDatetime: encounter.encounterDatetime,
|
|
49
|
+
transferDate: encounter.encounterDatetime,
|
|
50
|
+
fromLocation: encounter.location?.display || 'Not specified',
|
|
51
|
+
note: findObsValue(obs, transferNoteConceptUuid) || 'Not specified',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findObsValue(obs: Obs[], conceptUuid: string): string {
|
|
56
|
+
const observation = obs.find((o) => o.concept?.uuid === conceptUuid);
|
|
57
|
+
if (!observation) {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof observation.value === 'object' && observation.value !== null) {
|
|
62
|
+
return (observation.value as any).display || String(observation.value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return String(observation.value || '');
|
|
66
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { ModalBody, ModalHeader, Button, ModalFooter } from '@carbon/react';
|
|
4
|
+
import { formatDate, parseDate } from '@openmrs/esm-framework';
|
|
5
|
+
import type { TransferData } from './transfer-data.resource';
|
|
6
|
+
import styles from './transfer-details.scss';
|
|
7
|
+
|
|
8
|
+
interface TransferDetailsModalProps {
|
|
9
|
+
transferData: TransferData;
|
|
10
|
+
closeModal: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TransferDetailsModal: React.FC<TransferDetailsModalProps> = ({ transferData, closeModal }) => {
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<ModalHeader closeModal={closeModal} title={t('patientTransferInformation', 'Patient Transfer Information')} />
|
|
19
|
+
<ModalBody>
|
|
20
|
+
<div className={styles.container}>
|
|
21
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
22
|
+
<strong>{t('transferDate', 'Transfer Date')}:</strong>{' '}
|
|
23
|
+
{formatDate(parseDate(transferData.transferDate), { mode: 'wide' })}
|
|
24
|
+
</div>
|
|
25
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
26
|
+
<strong>{t('fromLocation', 'Transfer From')}:</strong> {transferData.fromLocation}
|
|
27
|
+
</div>
|
|
28
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
29
|
+
<strong>{t('transferNote', 'Transfer Note')}:</strong> {transferData.note}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</ModalBody>
|
|
33
|
+
<ModalFooter>
|
|
34
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
35
|
+
{t('close', 'Close')}
|
|
36
|
+
</Button>
|
|
37
|
+
</ModalFooter>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default TransferDetailsModal;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
5
|
+
|
|
6
|
+
.container {
|
|
7
|
+
padding: layout.$spacing-05;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.header {
|
|
11
|
+
margin-bottom: layout.$spacing-06;
|
|
12
|
+
|
|
13
|
+
h3 {
|
|
14
|
+
@include type.type-style('heading-03');
|
|
15
|
+
font-weight: 600;
|
|
16
|
+
margin: 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { DataTableSkeleton, InlineLoading } from '@carbon/react';
|
|
4
|
+
import { useLayoutType } from '@openmrs/esm-framework';
|
|
5
|
+
import { CardHeader, EmptyState, ErrorState, usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
6
|
+
import { useTransferNotes } from './transfer-notes.resource';
|
|
7
|
+
import TransferNotesTable from './transfer-notes-table.component';
|
|
8
|
+
import styles from './transfer-notes-overview.scss';
|
|
9
|
+
|
|
10
|
+
interface TransferNotesOverviewProps {
|
|
11
|
+
patientUuid: string;
|
|
12
|
+
patient: fhir.Patient;
|
|
13
|
+
basePath: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This extension displays transfer notes in the patient chart.
|
|
18
|
+
* It only shows transfer notes from the current visit.
|
|
19
|
+
* It uses the patient chart store and MUST only be mounted within the patient chart.
|
|
20
|
+
*/
|
|
21
|
+
const TransferNotesOverview: React.FC<TransferNotesOverviewProps> = ({ patientUuid, patient, basePath }) => {
|
|
22
|
+
const pageSize = 5;
|
|
23
|
+
const { t } = useTranslation();
|
|
24
|
+
const pageUrl = `\${openmrsSpaBase}/patient/${patient.id}/chart/Forms & Notes`;
|
|
25
|
+
const urlLabel = t('seeAll', 'See all');
|
|
26
|
+
|
|
27
|
+
// Get current visit from patient chart store
|
|
28
|
+
const { visitContext } = usePatientChartStore(patientUuid);
|
|
29
|
+
const visitUuid = visitContext?.uuid || null;
|
|
30
|
+
|
|
31
|
+
const displayText = t('transferNotes', 'Transfer notes');
|
|
32
|
+
const headerTitle = t('transferNotes', 'Transfer notes');
|
|
33
|
+
const { transferNotes, error, isLoading, isValidating } = useTransferNotes(patientUuid, visitUuid);
|
|
34
|
+
const layout = useLayoutType();
|
|
35
|
+
const isDesktop = layout === 'large-desktop' || layout === 'small-desktop';
|
|
36
|
+
|
|
37
|
+
// Don't show the widget if there's no current visit
|
|
38
|
+
if (!visitUuid) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isLoading) {
|
|
43
|
+
return <DataTableSkeleton role="progressbar" compact={isDesktop} zebra />;
|
|
44
|
+
}
|
|
45
|
+
if (error) {
|
|
46
|
+
return <ErrorState error={error} headerTitle={headerTitle} />;
|
|
47
|
+
}
|
|
48
|
+
// Only show the widget if there are transfer notes for the current visit
|
|
49
|
+
if (!transferNotes?.length) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={styles.widgetCard}>
|
|
55
|
+
<CardHeader title={headerTitle}>
|
|
56
|
+
<span>{isValidating ? <InlineLoading /> : null}</span>
|
|
57
|
+
</CardHeader>
|
|
58
|
+
<TransferNotesTable notes={transferNotes} pageSize={pageSize} urlLabel={urlLabel} pageUrl={pageUrl} />
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default TransferNotesOverview;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
5
|
+
|
|
6
|
+
.widgetCard {
|
|
7
|
+
background-color: $ui-background;
|
|
8
|
+
border: 1px solid colors.$gray-20;
|
|
9
|
+
position: relative;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.tableContainer {
|
|
13
|
+
padding: 0 layout.$spacing-05;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.container {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
margin: layout.$spacing-03;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.copy {
|
|
23
|
+
@include type.type-style('body-01');
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.content {
|
|
29
|
+
@include type.type-style('body-01');
|
|
30
|
+
white-space: pre-wrap;
|
|
31
|
+
word-break: break-word;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.observationRow {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
margin-bottom: layout.$spacing-03;
|
|
38
|
+
padding-bottom: layout.$spacing-03;
|
|
39
|
+
border-bottom: 1px solid colors.$gray-20;
|
|
40
|
+
|
|
41
|
+
&:last-of-type {
|
|
42
|
+
margin-bottom: 0;
|
|
43
|
+
padding-bottom: 0;
|
|
44
|
+
border-bottom: none;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.observationLabel {
|
|
49
|
+
@include type.type-style('label-01');
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
margin-bottom: layout.$spacing-02;
|
|
52
|
+
color: $text-02;
|
|
53
|
+
letter-spacing: 0.01em;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.observationValue {
|
|
57
|
+
@include type.type-style('body-01');
|
|
58
|
+
white-space: pre-wrap;
|
|
59
|
+
word-break: break-word;
|
|
60
|
+
line-height: 1.5;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.metadata {
|
|
64
|
+
@include type.type-style('label-01');
|
|
65
|
+
color: $text-02;
|
|
66
|
+
margin-top: layout.$spacing-05;
|
|
67
|
+
padding-top: layout.$spacing-03;
|
|
68
|
+
border-top: 1px solid colors.$gray-20;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.expandedRow {
|
|
72
|
+
> td {
|
|
73
|
+
padding-left: layout.$spacing-08 !important;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
> td > div {
|
|
77
|
+
max-height: max-content !important;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
th[colspan] td[colspan] > div:first-child {
|
|
81
|
+
padding: 0 layout.$spacing-05;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.hiddenRow {
|
|
86
|
+
display: none;
|
|
87
|
+
}
|
|
88
|
+
|