@palladium-ethiopia/esm-clinical-workflow-app 5.4.2-pre.20
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 +14 -0
- package/README.md +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/159.js +1 -0
- package/dist/159.js.map +1 -0
- package/dist/208.js +1 -0
- package/dist/208.js.map +1 -0
- package/dist/209.js +1 -0
- package/dist/209.js.map +1 -0
- package/dist/363.js +1 -0
- package/dist/363.js.map +1 -0
- package/dist/410.js +1 -0
- package/dist/410.js.map +1 -0
- package/dist/442.js +1 -0
- package/dist/442.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/484.js +11 -0
- package/dist/484.js.map +1 -0
- package/dist/540.js +1 -0
- package/dist/540.js.map +1 -0
- package/dist/545.js +43 -0
- package/dist/545.js.map +1 -0
- package/dist/61.js +1 -0
- package/dist/61.js.map +1 -0
- package/dist/677.js +1 -0
- package/dist/677.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/697.js +1 -0
- package/dist/697.js.map +1 -0
- package/dist/712.js +1 -0
- package/dist/712.js.map +1 -0
- package/dist/771.js +1 -0
- package/dist/771.js.map +1 -0
- package/dist/789.js +1 -0
- package/dist/789.js.map +1 -0
- package/dist/ethiopia-esm-clinical-workflow-app.js +6 -0
- package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +579 -0
- package/dist/ethiopia-esm-clinical-workflow-app.js.map +1 -0
- package/dist/main.js +16 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +3 -0
- package/package.json +59 -0
- package/rspack.config.js +1 -0
- package/src/config-schema.ts +69 -0
- package/src/constants.ts +2 -0
- package/src/createDashboardLink.tsx +10 -0
- package/src/dashboard.meta.ts +6 -0
- package/src/declarations.d.ts +3 -0
- package/src/helper.ts +115 -0
- package/src/index.ts +51 -0
- package/src/mru/billing-information/billing-information.resource.ts +139 -0
- package/src/mru/billing-information/billing-information.scss +55 -0
- package/src/mru/billing-information/billing-information.workspace.tsx +371 -0
- package/src/mru/dashboard.component.tsx +18 -0
- package/src/mru/mru.component.tsx +106 -0
- package/src/mru/mru.scss +28 -0
- package/src/patient-registration/patient-registration.resource.tsx +129 -0
- package/src/patient-registration/patient.registration.workspace.scss +47 -0
- package/src/patient-registration/patient.registration.workspace.tsx +443 -0
- package/src/patient-registration/useGenerateIdentifier.ts +26 -0
- package/src/patient-scoreboard/appointment-cards/checked-in-appointments.card.tsx +18 -0
- package/src/patient-scoreboard/appointment-cards/not-arrived-appointments.card.tsx +18 -0
- package/src/patient-scoreboard/appointment-cards/total-appointments.card.tsx +18 -0
- package/src/patient-scoreboard/hooks/useAppointmentList.ts +61 -0
- package/src/patient-scoreboard/hooks/useVisitList.ts +104 -0
- package/src/patient-scoreboard/metrics-card/metrics-card.component.scss +84 -0
- package/src/patient-scoreboard/metrics-card/metrics-card.component.tsx +40 -0
- package/src/patient-scoreboard/patient-scoreboard.component.scss +47 -0
- package/src/patient-scoreboard/patient-scoreboard.component.tsx +70 -0
- package/src/patient-scoreboard/visit-cards/active-visits.card.tsx +18 -0
- package/src/patient-scoreboard/visit-cards/scheduled-visits.card.tsx +18 -0
- package/src/patient-scoreboard/visit-cards/total-visits.card.tsx +18 -0
- package/src/patient-scoreboard/visits-table/visits-table.component.scss +31 -0
- package/src/patient-scoreboard/visits-table/visits-table.component.tsx +181 -0
- package/src/root.component.tsx +20 -0
- package/src/root.scss +10 -0
- package/src/routes.json +108 -0
- package/src/triage/patient-banner.component.tsx +59 -0
- package/src/triage/patient-banner.scss +14 -0
- package/src/triage/triage-dashboard.component.tsx +116 -0
- package/src/triage/triage-dashboard.scss +107 -0
- package/src/triage/triage.resource.tsx +44 -0
- package/src/triage/useStartVisitAndLaunchTriageForm.ts +156 -0
- package/src/types/index.ts +0 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ExtensionSlot, usePatient, useVisit } from '@openmrs/esm-framework';
|
|
3
|
+
import { Button, InlineLoading } from '@carbon/react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Close, Stethoscope } from '@carbon/react/icons';
|
|
6
|
+
import { useStartVisitAndLaunchTriageForm, launchTriageFormWorkspace } from './useStartVisitAndLaunchTriageForm';
|
|
7
|
+
|
|
8
|
+
import styles from './patient-banner.scss';
|
|
9
|
+
|
|
10
|
+
type PatientBannerProps = {
|
|
11
|
+
patientUuid: string;
|
|
12
|
+
formUuid: string;
|
|
13
|
+
formName: string;
|
|
14
|
+
setPatientUuid: (patientUuid: string | undefined) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const PatientBanner: React.FC<PatientBannerProps> = ({ patientUuid, formUuid, formName, setPatientUuid }) => {
|
|
18
|
+
const { t } = useTranslation();
|
|
19
|
+
const { isLoading: isVisitLoading, activeVisit } = useVisit(patientUuid);
|
|
20
|
+
const { handleStartVisitAndLaunchTriageForm } = useStartVisitAndLaunchTriageForm();
|
|
21
|
+
const { isLoading, error, patient } = usePatient(patientUuid);
|
|
22
|
+
|
|
23
|
+
const handleLaunchTriageForm = () => {
|
|
24
|
+
if (activeVisit) {
|
|
25
|
+
launchTriageFormWorkspace(patient, patientUuid, activeVisit, formUuid, formName, t);
|
|
26
|
+
} else {
|
|
27
|
+
handleStartVisitAndLaunchTriageForm(patientUuid, formUuid, formName);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (isLoading || isVisitLoading) {
|
|
32
|
+
return <InlineLoading description={t('loading', 'Loading...')} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className={styles.patientBannerContainer}>
|
|
37
|
+
<div className={styles.patientBannerHeader}>
|
|
38
|
+
<Button kind="ghost" renderIcon={Stethoscope} onClick={() => handleLaunchTriageForm()}>
|
|
39
|
+
{t('triageForm', 'Triage form')}
|
|
40
|
+
</Button>
|
|
41
|
+
<Button kind="danger--ghost" renderIcon={Close} onClick={() => setPatientUuid(undefined)}>
|
|
42
|
+
{t('close', 'Close')}
|
|
43
|
+
</Button>
|
|
44
|
+
</div>
|
|
45
|
+
{patient && (
|
|
46
|
+
<ExtensionSlot
|
|
47
|
+
name="patient-header-slot"
|
|
48
|
+
state={{
|
|
49
|
+
patient,
|
|
50
|
+
patientUuid: patientUuid,
|
|
51
|
+
hideActionsOverflow: true,
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default PatientBanner;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/colors';
|
|
3
|
+
|
|
4
|
+
.patientBannerContainer {
|
|
5
|
+
margin: layout.$spacing-05;
|
|
6
|
+
border: 1px solid colors.$gray-20;
|
|
7
|
+
background-color: colors.$white;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.patientBannerHeader {
|
|
11
|
+
display: flex;
|
|
12
|
+
justify-content: flex-end;
|
|
13
|
+
align-items: center;
|
|
14
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, InlineNotification, Tile } from '@carbon/react';
|
|
4
|
+
import { Add, Search } from '@carbon/react/icons';
|
|
5
|
+
import {
|
|
6
|
+
ExtensionSlot,
|
|
7
|
+
TriagePictogram,
|
|
8
|
+
launchWorkspace,
|
|
9
|
+
PageHeader,
|
|
10
|
+
useConfig,
|
|
11
|
+
useSession,
|
|
12
|
+
} from '@openmrs/esm-framework';
|
|
13
|
+
|
|
14
|
+
import styles from './triage-dashboard.scss';
|
|
15
|
+
import type { ClinicalWorkflowConfig } from '../config-schema';
|
|
16
|
+
import PatientBanner from './patient-banner.component';
|
|
17
|
+
import { getTriageFormForLocation } from './triage.resource';
|
|
18
|
+
|
|
19
|
+
const TriageDashboard: React.FC = () => {
|
|
20
|
+
const { t } = useTranslation();
|
|
21
|
+
const { sessionLocation } = useSession();
|
|
22
|
+
const { triageLocationForms } = useConfig<ClinicalWorkflowConfig>();
|
|
23
|
+
const [patientUuid, setPatientUuid] = useState<string | null>(null);
|
|
24
|
+
|
|
25
|
+
const triageFormConfig = getTriageFormForLocation(sessionLocation?.uuid, triageLocationForms);
|
|
26
|
+
|
|
27
|
+
if (!sessionLocation) {
|
|
28
|
+
return (
|
|
29
|
+
<div className={styles.triageDashboardContainer}>
|
|
30
|
+
<PageHeader
|
|
31
|
+
className={styles.pageHeader}
|
|
32
|
+
title={t('triageDashboard', 'Triage Dashboard')}
|
|
33
|
+
illustration={<TriagePictogram />}
|
|
34
|
+
/>
|
|
35
|
+
<InlineNotification
|
|
36
|
+
kind="error"
|
|
37
|
+
title={t('noSessionLocation', 'No session location')}
|
|
38
|
+
subtitle={t('noSessionLocationSubtitle', 'Please select a location to continue')}
|
|
39
|
+
lowContrast
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!triageFormConfig) {
|
|
46
|
+
return (
|
|
47
|
+
<div className={styles.triageDashboardContainer}>
|
|
48
|
+
<PageHeader
|
|
49
|
+
className={styles.pageHeader}
|
|
50
|
+
title={t('triageDashboard', 'Triage Dashboard')}
|
|
51
|
+
illustration={<TriagePictogram />}
|
|
52
|
+
/>
|
|
53
|
+
<InlineNotification
|
|
54
|
+
kind="warning"
|
|
55
|
+
title={t('noTriageFormConfigured', 'No triage form configured')}
|
|
56
|
+
subtitle={t('noTriageFormConfiguredSubtitle', 'No triage form is configured for location: {{location}}', {
|
|
57
|
+
location: sessionLocation.display,
|
|
58
|
+
})}
|
|
59
|
+
lowContrast
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className={styles.triageDashboardContainer}>
|
|
67
|
+
<PageHeader
|
|
68
|
+
className={styles.pageHeader}
|
|
69
|
+
title={t('triageDashboard', 'Triage Dashboard')}
|
|
70
|
+
illustration={<TriagePictogram />}
|
|
71
|
+
/>
|
|
72
|
+
<div className={styles.headerActions}>
|
|
73
|
+
<ExtensionSlot
|
|
74
|
+
className={styles.patientSearchBar}
|
|
75
|
+
name="patient-search-bar-slot"
|
|
76
|
+
state={{
|
|
77
|
+
selectPatientAction: (patientUuid: string) => setPatientUuid(patientUuid),
|
|
78
|
+
buttonProps: {
|
|
79
|
+
kind: 'secondary',
|
|
80
|
+
},
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
<Button onClick={() => launchWorkspace('patient-registration-workspace')} kind="tertiary" renderIcon={Add}>
|
|
84
|
+
{t('registerNewPatient', 'Register New Patient')}
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
{!patientUuid ? (
|
|
88
|
+
<div className={styles.emptyStateContainer}>
|
|
89
|
+
<Tile className={styles.emptyStateTile}>
|
|
90
|
+
<div className={styles.emptyStateContent}>
|
|
91
|
+
<div className={styles.emptyStateIcon}>
|
|
92
|
+
<Search size={48} />
|
|
93
|
+
</div>
|
|
94
|
+
<h3 className={styles.emptyStateHeading}>{t('noPatientSelected', 'No patient selected')}</h3>
|
|
95
|
+
<p className={styles.emptyStateDescription}>
|
|
96
|
+
{t(
|
|
97
|
+
'searchForPatientToStartTriage',
|
|
98
|
+
'Search for a patient using the search bar above to start the triage process, or register a new patient.',
|
|
99
|
+
)}
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
</Tile>
|
|
103
|
+
</div>
|
|
104
|
+
) : (
|
|
105
|
+
<PatientBanner
|
|
106
|
+
patientUuid={patientUuid}
|
|
107
|
+
formUuid={triageFormConfig.formUuid}
|
|
108
|
+
formName={triageFormConfig.name}
|
|
109
|
+
setPatientUuid={setPatientUuid}
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default TriageDashboard;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/colors';
|
|
3
|
+
|
|
4
|
+
.contentSwitcher {
|
|
5
|
+
margin: layout.$spacing-05;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.headerActions {
|
|
9
|
+
display: flex;
|
|
10
|
+
column-gap: layout.$spacing-05;
|
|
11
|
+
margin: layout.$spacing-05;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.patientSearchBar {
|
|
15
|
+
width: 100%;
|
|
16
|
+
|
|
17
|
+
& form {
|
|
18
|
+
border: none;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.pageHeader {
|
|
23
|
+
border-bottom: 1px solid colors.$gray-20;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.disabled {
|
|
27
|
+
opacity: 0.5;
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
|
|
30
|
+
button {
|
|
31
|
+
opacity: 1;
|
|
32
|
+
pointer-events: auto;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.emptyStateContainer {
|
|
37
|
+
margin: layout.$spacing-05;
|
|
38
|
+
display: flex;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
align-items: center;
|
|
41
|
+
min-height: 400px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.emptyStateTile {
|
|
45
|
+
width: 100%;
|
|
46
|
+
padding: layout.$spacing-07;
|
|
47
|
+
text-align: center;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.emptyStateContent {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: layout.$spacing-05;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.emptyStateIcon {
|
|
58
|
+
color: colors.$gray-50;
|
|
59
|
+
margin-bottom: layout.$spacing-03;
|
|
60
|
+
|
|
61
|
+
:global(svg) {
|
|
62
|
+
display: block;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.emptyStateHeading {
|
|
67
|
+
font-size: 1.25rem;
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
margin: 0;
|
|
70
|
+
color: colors.$gray-100;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.emptyStateDescription {
|
|
74
|
+
font-size: 0.875rem;
|
|
75
|
+
color: colors.$gray-70;
|
|
76
|
+
margin: 0;
|
|
77
|
+
line-height: 1.5;
|
|
78
|
+
max-width: 500px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.emptyStateActions {
|
|
82
|
+
margin-top: layout.$spacing-05;
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
gap: layout.$spacing-03;
|
|
86
|
+
text-align: left;
|
|
87
|
+
width: 100%;
|
|
88
|
+
max-width: 500px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.actionItem {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: flex-start;
|
|
94
|
+
gap: layout.$spacing-03;
|
|
95
|
+
font-size: 0.875rem;
|
|
96
|
+
color: colors.$gray-70;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.actionIcon {
|
|
100
|
+
flex-shrink: 0;
|
|
101
|
+
color: colors.$blue-60;
|
|
102
|
+
margin-top: 2px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.actionLabel {
|
|
106
|
+
line-height: 1.5;
|
|
107
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl, Visit } from '@openmrs/esm-framework';
|
|
2
|
+
import type { ClinicalWorkflowConfig } from '../config-schema';
|
|
3
|
+
|
|
4
|
+
const queueEntryCustomRepresentation =
|
|
5
|
+
'custom:(uuid,display,queue,status,patient:(uuid,display,person,identifiers:(uuid,display,identifier,identifierType)),visit:(uuid,display,startDatetime,encounters:(uuid,display,diagnoses,encounterDatetime,encounterType,obs,encounterProviders,voided),attributes:(uuid,display,value,attributeType)),priority,priorityComment,sortWeight,startedAt,endedAt,locationWaitingFor,queueComingFrom,providerWaitingFor,previousQueueEntry)';
|
|
6
|
+
|
|
7
|
+
export const createVisitForPatient = async (patientUuid: string, visitTypeUuid: string) => {
|
|
8
|
+
const url = `${restBaseUrl}/visit?v=full`;
|
|
9
|
+
const payload = {
|
|
10
|
+
patient: patientUuid,
|
|
11
|
+
visitType: visitTypeUuid,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return openmrsFetch<Visit>(url, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify(payload),
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getCurrentVisitForPatient = async (patientUuid: string): Promise<Visit | undefined> => {
|
|
24
|
+
const url = `${restBaseUrl}/visit?v=full&patient=${patientUuid}&includeInactive=false`;
|
|
25
|
+
const { data } = await openmrsFetch<{ results: Array<Visit> }>(url);
|
|
26
|
+
const currentVisit = data.results?.find((visit) => visit.stopDatetime === null);
|
|
27
|
+
return currentVisit;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const fetchQueueEntryForPatient = async (patientUuid: string): Promise<any | undefined> => {
|
|
31
|
+
const url = `${restBaseUrl}/queue-entry?v=${queueEntryCustomRepresentation}&patient=${patientUuid}&includeInactive=false`;
|
|
32
|
+
const { data } = await openmrsFetch<{ results: Array<unknown> }>(url);
|
|
33
|
+
return data.results[0];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const getTriageFormForLocation = (
|
|
37
|
+
locationUuid: string | undefined,
|
|
38
|
+
triageLocationForms: ClinicalWorkflowConfig['triageLocationForms'],
|
|
39
|
+
): { formUuid: string; name: string } | undefined => {
|
|
40
|
+
if (!locationUuid) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return triageLocationForms[locationUuid];
|
|
44
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import type { TFunction } from 'i18next';
|
|
4
|
+
import {
|
|
5
|
+
Encounter,
|
|
6
|
+
fetchCurrentPatient,
|
|
7
|
+
launchWorkspace2,
|
|
8
|
+
showModal,
|
|
9
|
+
showSnackbar,
|
|
10
|
+
useConfig,
|
|
11
|
+
Visit,
|
|
12
|
+
} from '@openmrs/esm-framework';
|
|
13
|
+
|
|
14
|
+
import { createVisitForPatient, getCurrentVisitForPatient } from './triage.resource';
|
|
15
|
+
import type { ClinicalWorkflowConfig } from '../config-schema';
|
|
16
|
+
|
|
17
|
+
export const launchTriageFormWorkspace = (
|
|
18
|
+
patient: Awaited<ReturnType<typeof fetchCurrentPatient>>,
|
|
19
|
+
patientUuid: string,
|
|
20
|
+
visit: Visit,
|
|
21
|
+
formUuid: string,
|
|
22
|
+
formName: string,
|
|
23
|
+
t: TFunction<'translation', undefined>,
|
|
24
|
+
) => {
|
|
25
|
+
const handleShowModal = (encounter: Encounter) => {
|
|
26
|
+
const dispose = showModal('transition-patient-to-latest-queue-modal', {
|
|
27
|
+
activeVisit: visit,
|
|
28
|
+
closeModal: () => dispose(),
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (!visit?.uuid || !visit?.visitType?.uuid) {
|
|
33
|
+
throw new Error('Invalid visit data received');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Launch triage form workspace
|
|
37
|
+
launchWorkspace2(
|
|
38
|
+
'clinical-workflow-patient-form-entry-workspace',
|
|
39
|
+
{
|
|
40
|
+
formEntryWorkspaceName: formName,
|
|
41
|
+
patient,
|
|
42
|
+
visitContext: visit,
|
|
43
|
+
form: {
|
|
44
|
+
visitUuid: visit.uuid,
|
|
45
|
+
uuid: formUuid,
|
|
46
|
+
visitTypeUuid: visit.visitType.uuid,
|
|
47
|
+
},
|
|
48
|
+
encounterUuid: '',
|
|
49
|
+
handlePostResponse: handleShowModal,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
patientUuid: patientUuid,
|
|
53
|
+
patient: patient,
|
|
54
|
+
visitContext: visit,
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Set z-index for workspace container
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
const workspaceContainer = document.getElementById('omrs-workspaces-container');
|
|
61
|
+
if (workspaceContainer) {
|
|
62
|
+
workspaceContainer.style.zIndex = '100';
|
|
63
|
+
}
|
|
64
|
+
}, 0);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
interface UseStartVisitAndLaunchTriageFormReturn {
|
|
68
|
+
handleStartVisitAndLaunchTriageForm: (patientUuid: string, formUuid: string, formName: string) => Promise<void>;
|
|
69
|
+
isLoading: boolean;
|
|
70
|
+
error: Error | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const useStartVisitAndLaunchTriageForm = (): UseStartVisitAndLaunchTriageFormReturn => {
|
|
74
|
+
const { t } = useTranslation();
|
|
75
|
+
const { visitTypeUuid } = useConfig<ClinicalWorkflowConfig>();
|
|
76
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
77
|
+
const [error, setError] = useState<Error | null>(null);
|
|
78
|
+
|
|
79
|
+
const handleStartVisitAndLaunchTriageForm = useCallback(
|
|
80
|
+
async (patientUuid: string, formUuid: string, formName: string) => {
|
|
81
|
+
if (!patientUuid?.trim()) {
|
|
82
|
+
const validationError = new Error('Patient UUID is required');
|
|
83
|
+
setError(validationError);
|
|
84
|
+
showSnackbar({
|
|
85
|
+
title: t('triageDashboardError', 'Error'),
|
|
86
|
+
kind: 'error',
|
|
87
|
+
subtitle: t('triageDashboardInvalidPatientUuid', 'Invalid patient identifier'),
|
|
88
|
+
isLowContrast: true,
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!formUuid?.trim()) {
|
|
94
|
+
const validationError = new Error('Form UUID is required');
|
|
95
|
+
setError(validationError);
|
|
96
|
+
showSnackbar({
|
|
97
|
+
title: t('triageDashboardError', 'Error'),
|
|
98
|
+
kind: 'error',
|
|
99
|
+
subtitle: t('triageDashboardInvalidFormUuid', 'Invalid form identifier'),
|
|
100
|
+
isLowContrast: true,
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setIsLoading(true);
|
|
106
|
+
setError(null);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Fetch patient data
|
|
110
|
+
const patient = await fetchCurrentPatient(patientUuid);
|
|
111
|
+
|
|
112
|
+
if (!patient) {
|
|
113
|
+
throw new Error('Failed to fetch patient data');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let visit = await getCurrentVisitForPatient(patientUuid);
|
|
117
|
+
if (!visit) {
|
|
118
|
+
const visitResponse = await createVisitForPatient(patientUuid, visitTypeUuid);
|
|
119
|
+
|
|
120
|
+
if (!visitResponse.ok) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
visitResponse.data?.error?.message ||
|
|
123
|
+
t('triageDashboardErrorStartingVisit', 'Error starting visit for patient'),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
visit = visitResponse.data;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Launch triage form workspace with visit
|
|
131
|
+
launchTriageFormWorkspace(patient, patientUuid, visit, formUuid, formName, t);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
const errorMessage =
|
|
134
|
+
err instanceof Error ? err.message : t('triageDashboardUnexpectedError', 'An unexpected error occurred');
|
|
135
|
+
|
|
136
|
+
setError(err instanceof Error ? err : new Error(errorMessage));
|
|
137
|
+
|
|
138
|
+
showSnackbar({
|
|
139
|
+
title: t('triageDashboardErrorStartingVisit', 'Error starting visit for patient'),
|
|
140
|
+
kind: 'error',
|
|
141
|
+
subtitle: errorMessage,
|
|
142
|
+
isLowContrast: true,
|
|
143
|
+
});
|
|
144
|
+
} finally {
|
|
145
|
+
setIsLoading(false);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
[t, visitTypeUuid],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
handleStartVisitAndLaunchTriageForm,
|
|
153
|
+
isLoading,
|
|
154
|
+
error,
|
|
155
|
+
};
|
|
156
|
+
};
|
|
File without changes
|
package/tsconfig.json
ADDED