@kenyaemr/esm-care-panel-app 5.4.2-pre.2599 → 5.4.2-pre.2606
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/127.js +1 -1
- package/dist/40.js +1 -1
- package/dist/489.js +1 -0
- package/dist/489.js.map +1 -0
- package/dist/{124.js → 661.js} +15 -15
- package/dist/661.js.map +1 -0
- package/dist/916.js +1 -1
- package/dist/kenyaemr-esm-care-panel-app.js +3 -3
- package/dist/kenyaemr-esm-care-panel-app.js.buildmanifest.json +62 -62
- package/dist/main.js +4 -4
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/care-programs/care-program.resource.ts +211 -0
- package/src/care-programs/care-programs.component.tsx +84 -15
- package/src/care-programs/care-programs.scss +44 -3
- package/src/care-programs/program.workspace.tsx +181 -0
- package/src/hooks/useCarePrograms.tsx +12 -1
- package/src/index.ts +2 -0
- package/src/routes.json +7 -0
- package/translations/am.json +1 -0
- package/translations/en.json +1 -0
- package/translations/sw.json +1 -0
- package/dist/124.js.map +0 -1
- package/dist/835.js +0 -1
- package/dist/835.js.map +0 -1
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[],"extensions":[{"name":"care-panel-patient-summary","component":"carePanelPatientSummary","slot":"patient-chart-care-panel-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"care-panel-summary-dashboard-link","component":"carePanelSummaryDashboardLink","order":3,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-care-panel-dashboard-slot","layoutMode":"anchored","path":"Care panel"}},{"name":"delete-regimen-confirmation-dialog","component":"deleteRegimenConfirmationDialog"},{"name":"hiv-patient-visit-summary-dashboard-link","component":"hivPatientSummaryDashboardLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hiv-patient-summary-slot","path":"HIV Patient Summary","layoutMode":"anchored"}},{"name":"hiv-patient-visit-summary","slot":"hiv-patient-summary-slot","component":"hivPatientSummary","order":3,"online":true,"offline":false},{"name":"dispensing-patient-vitals","component":"dispensingPaentientVitals","slot":"dispensing-condition-and-diagnoses","order":1,"online":true,"offline":true},{"name":"patient-discharge-side-rail-icon","component":"patientDischargeSideRailIcon","slot":"action-menu-ward-patient-items-slot"}],"workspaces":[{"name":"patient-regimen-workspace","title":"Patient Regimen","component":"regimenFormWorkspace","type":"form","canMaximize":true,"canHide":true,"width":"wider"},{"name":"patient-care-discharge-workspace","title":"Patient Discharge","component":"patientDischargeWorkspace","type":"workspace","canMaximize":true,"canHide":true,"width":"extra-wide","groups":["ward-patient"]}],"version":"5.4.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[],"extensions":[{"name":"care-panel-patient-summary","component":"carePanelPatientSummary","slot":"patient-chart-care-panel-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"care-panel-summary-dashboard-link","component":"carePanelSummaryDashboardLink","order":3,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-care-panel-dashboard-slot","layoutMode":"anchored","path":"Care panel"}},{"name":"delete-regimen-confirmation-dialog","component":"deleteRegimenConfirmationDialog"},{"name":"hiv-patient-visit-summary-dashboard-link","component":"hivPatientSummaryDashboardLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hiv-patient-summary-slot","path":"HIV Patient Summary","layoutMode":"anchored"}},{"name":"hiv-patient-visit-summary","slot":"hiv-patient-summary-slot","component":"hivPatientSummary","order":3,"online":true,"offline":false},{"name":"dispensing-patient-vitals","component":"dispensingPaentientVitals","slot":"dispensing-condition-and-diagnoses","order":1,"online":true,"offline":true},{"name":"patient-discharge-side-rail-icon","component":"patientDischargeSideRailIcon","slot":"action-menu-ward-patient-items-slot"}],"workspaces":[{"name":"patient-regimen-workspace","title":"Patient Regimen","component":"regimenFormWorkspace","type":"form","canMaximize":true,"canHide":true,"width":"wider"},{"name":"patient-care-discharge-workspace","title":"Patient Discharge","component":"patientDischargeWorkspace","type":"workspace","canMaximize":true,"canHide":true,"width":"extra-wide","groups":["ward-patient"]},{"name":"mch-program-form-workspace","component":"mchProgramForm","title":"Program Form","type":"workspace"}],"version":"5.4.2-pre.2606"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { FetchResponse, launchWorkspace, openmrsFetch, restBaseUrl, showModal } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR, { mutate } from 'swr';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
|
|
5
|
+
export interface Enrollement {
|
|
6
|
+
uuid: string;
|
|
7
|
+
display: string;
|
|
8
|
+
program: Program;
|
|
9
|
+
dateEnrolled: string;
|
|
10
|
+
dateCompleted?: string;
|
|
11
|
+
location: Location;
|
|
12
|
+
states: Array<ProgramWorkflowState>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Program {
|
|
16
|
+
display: string;
|
|
17
|
+
name: string;
|
|
18
|
+
uuid: string;
|
|
19
|
+
retired: boolean;
|
|
20
|
+
description: string;
|
|
21
|
+
concept: Concept;
|
|
22
|
+
allWorkflows: any[];
|
|
23
|
+
outcomesConcept: any;
|
|
24
|
+
resourceVersion: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Concept {
|
|
28
|
+
uuid: string;
|
|
29
|
+
display: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Location {
|
|
33
|
+
uuid: string;
|
|
34
|
+
display: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ProgramWorkflowState {
|
|
38
|
+
state: {
|
|
39
|
+
uuid: string;
|
|
40
|
+
concept: Concept;
|
|
41
|
+
};
|
|
42
|
+
startDate: string;
|
|
43
|
+
endDate: string;
|
|
44
|
+
voided: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const programForms = [
|
|
48
|
+
{
|
|
49
|
+
programName: 'PNC',
|
|
50
|
+
programUuid: '286598d5-1886-4f0d-9e5f-fa5473399cee',
|
|
51
|
+
forms: [
|
|
52
|
+
{ formName: 'MCH Postnatal Visit Form', formUuId: '72aa78e0-ee4b-47c3-9073-26f3b9ecc4a7' },
|
|
53
|
+
{ formName: 'PNC Services Discontinuation', formUuId: '30db888b-d6d3-47fb-b0c9-dbdf10a57ff5' },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
programName: 'MCH - Child Services',
|
|
58
|
+
programUuid: 'c2ecdf11-97cd-432a-a971-cfd9bd296b83',
|
|
59
|
+
forms: [
|
|
60
|
+
{ formName: 'Child welafare clinic form', formUuId: '755b59e6-acbb-4853-abaf-be302039f902' },
|
|
61
|
+
{ formName: 'MCH Postnatal Visit Form', formUuId: '72aa78e0-ee4b-47c3-9073-26f3b9ecc4a7' },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
programName: 'Nutrition',
|
|
66
|
+
programUuid: '504f179b-4a13-4790-9ecd-ca4963448af8',
|
|
67
|
+
forms: [
|
|
68
|
+
{ formName: 'Nutrition form', formUuId: 'b8357314-0f6a-4fc9-a5b7-339f47095d62' },
|
|
69
|
+
{ formName: 'Nutrition Services Discontinuation', formUuId: '0648a046-f404-4246-806f-c9ee78232d6d' },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
programName: 'Family Planning',
|
|
74
|
+
programUuid: '191269d2-9973-4958-9936-f687ed771050',
|
|
75
|
+
forms: [
|
|
76
|
+
{ formName: 'Family Planning Form', formUuId: 'a52c57d4-110f-4879-82ae-907b0d90add6' },
|
|
77
|
+
{ formName: 'Family Planning Discontinuation', formUuId: 'efc782ea-9a16-4791-824a-18be7417eda4' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
// { // TODO Uncomment and add conrect uuids after program is added by the BA
|
|
81
|
+
// programName: 'Pre-Conception care program',
|
|
82
|
+
// programUuid: '191269d2-9973-4958-9936-f687ed771050',
|
|
83
|
+
// forms: [
|
|
84
|
+
// { formName: 'Family Planning Form', formUuId: 'a52c57d4-110f-4879-82ae-907b0d90add6' },
|
|
85
|
+
// { formName: 'Family Planning Discontinuation', formUuId: 'efc782ea-9a16-4791-824a-18be7417eda4' },
|
|
86
|
+
// ],
|
|
87
|
+
// },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
export const getProgramForms = (programUuid: string) => {
|
|
91
|
+
const program = programForms.find((p) => p.programUuid === programUuid);
|
|
92
|
+
if (!program) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
return program.forms;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const usePrograms = () => {
|
|
99
|
+
const rep = 'custom:(uuid,display,name,allWorkflows,concept:(uuid,display))';
|
|
100
|
+
const url = `${restBaseUrl}/program?v=${rep}`;
|
|
101
|
+
const { data, error, isLoading, mutate } = useSWR<FetchResponse<{ results: Array<Program> }>>(url, openmrsFetch);
|
|
102
|
+
return {
|
|
103
|
+
isLoading,
|
|
104
|
+
error,
|
|
105
|
+
programs: data?.data?.results ?? [],
|
|
106
|
+
mutate,
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const usePatientEnrolledPrograms = (patientUuid: string) => {
|
|
111
|
+
const customRepresentation = `custom:(uuid,display,program,dateEnrolled,dateCompleted,location:(uuid,display),states:(startDate,endDate,voided,state:(uuid,concept:(display))))`;
|
|
112
|
+
|
|
113
|
+
const url = `${restBaseUrl}/programenrollment?patient=${patientUuid}&v=${customRepresentation}`;
|
|
114
|
+
const { data, error, isLoading, mutate } = useSWR<FetchResponse<{ results: Array<Enrollement> }>>(url, openmrsFetch);
|
|
115
|
+
return {
|
|
116
|
+
isLoading,
|
|
117
|
+
error,
|
|
118
|
+
enrollments: data?.data?.results ?? [],
|
|
119
|
+
mutate,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const useProgramDetail = (programId: string) => {
|
|
124
|
+
const rep = 'custom:(uuid,display,name,allWorkflows,concept:(uuid,display))';
|
|
125
|
+
const url = `${restBaseUrl}/program/${programId}?v=${rep}`;
|
|
126
|
+
const { data, error, isLoading, mutate } = useSWR<FetchResponse<Program>>(url, openmrsFetch);
|
|
127
|
+
return {
|
|
128
|
+
isLoading,
|
|
129
|
+
error,
|
|
130
|
+
program: data?.data,
|
|
131
|
+
mutate,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const ProgramFormSchema = z.object({
|
|
136
|
+
dateEnrolled: z.date({ coerce: true }),
|
|
137
|
+
dateCompleted: z.date({ coerce: true }).optional(),
|
|
138
|
+
location: z.string().nonempty(),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export type ProgramFormData = z.infer<typeof ProgramFormSchema>;
|
|
142
|
+
|
|
143
|
+
export const mutateEnrollments = (patientUuid: string) =>
|
|
144
|
+
mutate((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/programenrollment?patient=${patientUuid}`));
|
|
145
|
+
|
|
146
|
+
export function createProgramEnrollment(
|
|
147
|
+
program: Program,
|
|
148
|
+
patientUuid: string,
|
|
149
|
+
payload: ProgramFormData,
|
|
150
|
+
abortController: AbortController,
|
|
151
|
+
) {
|
|
152
|
+
const { dateEnrolled, dateCompleted, location } = payload;
|
|
153
|
+
return openmrsFetch(`${restBaseUrl}/programenrollment`, {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: {
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
},
|
|
158
|
+
body: { program, patient: patientUuid, dateEnrolled, dateCompleted, location },
|
|
159
|
+
signal: abortController.signal,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function updateProgramEnrollment(
|
|
164
|
+
programEnrollmentUuid: string,
|
|
165
|
+
payload: ProgramFormData,
|
|
166
|
+
abortController: AbortController,
|
|
167
|
+
) {
|
|
168
|
+
const { dateEnrolled, dateCompleted, location } = payload;
|
|
169
|
+
return openmrsFetch(`${restBaseUrl}/programenrollment/${programEnrollmentUuid}`, {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: {
|
|
172
|
+
'Content-Type': 'application/json',
|
|
173
|
+
},
|
|
174
|
+
body: { dateEnrolled, dateCompleted, location },
|
|
175
|
+
signal: abortController.signal,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const findLastState = (states: ProgramWorkflowState[]): ProgramWorkflowState => {
|
|
180
|
+
const activeStates = states.filter((state) => !state.voided);
|
|
181
|
+
const ongoingState = activeStates.find((state) => !state.endDate);
|
|
182
|
+
|
|
183
|
+
if (ongoingState) {
|
|
184
|
+
return ongoingState;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return activeStates.sort((a, b) => new Date(b.endDate).getTime() - new Date(a.endDate).getTime())[0];
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const launchDeleteProgramDialog = (programEnrollmentId: string, patientUuid: string) => {
|
|
191
|
+
const dispose = showModal('program-delete-confirmation-modal', {
|
|
192
|
+
closeDeleteModal: () => dispose(),
|
|
193
|
+
programEnrollmentId,
|
|
194
|
+
patientUuid,
|
|
195
|
+
size: 'sm',
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const launchProgramForm = (
|
|
200
|
+
programUuid: string,
|
|
201
|
+
patientUuid: string,
|
|
202
|
+
enrollment: Enrollement | undefined,
|
|
203
|
+
onSuccess?: () => void,
|
|
204
|
+
) => {
|
|
205
|
+
launchWorkspace('mch-program-form-workspace', {
|
|
206
|
+
enrollment,
|
|
207
|
+
patientUuid,
|
|
208
|
+
programUuid,
|
|
209
|
+
onSubmitSuccess: onSuccess,
|
|
210
|
+
});
|
|
211
|
+
};
|
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
import React, { useCallback, useMemo } from 'react';
|
|
2
1
|
import {
|
|
3
|
-
InlineLoading,
|
|
4
2
|
Button,
|
|
5
3
|
DataTable,
|
|
4
|
+
DataTableSkeleton,
|
|
5
|
+
InlineLoading,
|
|
6
|
+
OverflowMenu,
|
|
7
|
+
OverflowMenuItem,
|
|
6
8
|
Table,
|
|
7
|
-
TableHeader,
|
|
8
|
-
TableRow,
|
|
9
|
-
TableHead,
|
|
10
9
|
TableBody,
|
|
11
10
|
TableCell,
|
|
12
11
|
TableContainer,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableHeader,
|
|
14
|
+
TableRow,
|
|
15
|
+
Tag,
|
|
13
16
|
Tile,
|
|
14
|
-
DataTableSkeleton,
|
|
15
17
|
} from '@carbon/react';
|
|
16
18
|
import { Close, DocumentAdd } from '@carbon/react/icons';
|
|
17
|
-
import { CardHeader, EmptyState, launchStartVisitPrompt, ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
18
|
-
import { useTranslation } from 'react-i18next';
|
|
19
|
-
import { PatientCarePrograms, useCarePrograms } from '../hooks/useCarePrograms';
|
|
20
19
|
import { formatDate, launchWorkspace, restBaseUrl, useLayoutType, useVisit } from '@openmrs/esm-framework';
|
|
20
|
+
import { CardHeader, EmptyState, ErrorState, launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib';
|
|
21
21
|
import capitalize from 'lodash/capitalize';
|
|
22
|
+
import React, { useCallback, useMemo } from 'react';
|
|
23
|
+
import { useTranslation } from 'react-i18next';
|
|
22
24
|
import { mutate } from 'swr';
|
|
25
|
+
import { PatientCarePrograms, useCarePrograms } from '../hooks/useCarePrograms';
|
|
23
26
|
|
|
27
|
+
import {
|
|
28
|
+
getProgramForms,
|
|
29
|
+
launchDeleteProgramDialog,
|
|
30
|
+
launchProgramForm,
|
|
31
|
+
usePatientEnrolledPrograms,
|
|
32
|
+
} from './care-program.resource';
|
|
24
33
|
import styles from './care-programs.scss';
|
|
25
34
|
|
|
26
35
|
type CareProgramsProps = {
|
|
@@ -30,7 +39,13 @@ type CareProgramsProps = {
|
|
|
30
39
|
const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
|
|
31
40
|
const { t } = useTranslation();
|
|
32
41
|
const { currentVisit } = useVisit(patientUuid);
|
|
33
|
-
const {
|
|
42
|
+
const { eligibleCarePrograms, isLoading, isValidating, error, mutateEligiblePrograms } = useCarePrograms(patientUuid);
|
|
43
|
+
const {
|
|
44
|
+
enrollments,
|
|
45
|
+
isLoading: isLoadingEnrollments,
|
|
46
|
+
error: enrollmentsError,
|
|
47
|
+
mutate: mutateEnrollments,
|
|
48
|
+
} = usePatientEnrolledPrograms(patientUuid);
|
|
34
49
|
const isTablet = useLayoutType() === 'tablet';
|
|
35
50
|
|
|
36
51
|
const handleMutations = useCallback(() => {
|
|
@@ -75,8 +90,55 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
|
|
|
75
90
|
);
|
|
76
91
|
|
|
77
92
|
const rows = useMemo(
|
|
78
|
-
() =>
|
|
79
|
-
|
|
93
|
+
() => [
|
|
94
|
+
...enrollments.map((enrollment) => {
|
|
95
|
+
const forms = getProgramForms(enrollment.program.uuid);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
id: enrollment.program.uuid,
|
|
99
|
+
programName: enrollment.program.name,
|
|
100
|
+
status: (
|
|
101
|
+
<div className={styles.careProgramButtonContainer}>
|
|
102
|
+
<Tag type="green">Enrolled</Tag>
|
|
103
|
+
<OverflowMenu aria-label="overflow-menu" flipped>
|
|
104
|
+
{forms.map((form) => (
|
|
105
|
+
<OverflowMenuItem
|
|
106
|
+
key={form.formUuId}
|
|
107
|
+
itemText={form.formName}
|
|
108
|
+
onClick={() => {
|
|
109
|
+
currentVisit
|
|
110
|
+
? launchWorkspace('patient-form-entry-workspace', {
|
|
111
|
+
workspaceTitle: form.formName,
|
|
112
|
+
mutateForm: () => {
|
|
113
|
+
mutateEnrollments();
|
|
114
|
+
mutateEligiblePrograms();
|
|
115
|
+
},
|
|
116
|
+
formInfo: {
|
|
117
|
+
encounterUuid: '',
|
|
118
|
+
formUuid: form.formUuId,
|
|
119
|
+
// additionalProps: { enrollmenrDetails: careProgram.enrollmentDetails ?? {} },
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
: launchStartVisitPrompt();
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
))}
|
|
126
|
+
<OverflowMenuItem
|
|
127
|
+
itemText={t('edit', 'Edit')}
|
|
128
|
+
onClick={() =>
|
|
129
|
+
launchProgramForm(enrollment.program.uuid, patientUuid, enrollment, () => mutateEnrollments())
|
|
130
|
+
}
|
|
131
|
+
/>
|
|
132
|
+
<OverflowMenuItem
|
|
133
|
+
itemText={t('delete', 'Delete')}
|
|
134
|
+
onClick={() => launchDeleteProgramDialog(enrollment.uuid, patientUuid)}
|
|
135
|
+
/>
|
|
136
|
+
</OverflowMenu>
|
|
137
|
+
</div>
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
}),
|
|
141
|
+
...eligibleCarePrograms.map((careProgram) => {
|
|
80
142
|
return {
|
|
81
143
|
id: `${careProgram.uuid}`,
|
|
82
144
|
programName: careProgram.display,
|
|
@@ -96,7 +158,13 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
|
|
|
96
158
|
className="cds--btn--sm cds--layout--size-sm"
|
|
97
159
|
kind={careProgram.enrollmentStatus == 'active' ? 'danger--ghost' : 'ghost'}
|
|
98
160
|
iconDescription="Dismiss"
|
|
99
|
-
onClick={() => handleCareProgramClick(careProgram)}
|
|
161
|
+
// onClick={() => handleCareProgramClick(careProgram)}
|
|
162
|
+
onClick={() =>
|
|
163
|
+
launchProgramForm(careProgram.uuid, patientUuid, undefined, () => {
|
|
164
|
+
mutateEnrollments();
|
|
165
|
+
mutateEligiblePrograms();
|
|
166
|
+
})
|
|
167
|
+
}
|
|
100
168
|
renderIcon={careProgram.enrollmentStatus == 'active' ? Close : DocumentAdd}>
|
|
101
169
|
{careProgram.enrollmentStatus == 'active' ? 'Discontinue' : 'Enroll'}
|
|
102
170
|
</Button>
|
|
@@ -104,7 +172,8 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
|
|
|
104
172
|
),
|
|
105
173
|
};
|
|
106
174
|
}),
|
|
107
|
-
|
|
175
|
+
],
|
|
176
|
+
[enrollments, eligibleCarePrograms, t, currentVisit, mutateEnrollments, patientUuid, mutateEligiblePrograms],
|
|
108
177
|
);
|
|
109
178
|
|
|
110
179
|
const headers = [
|
|
@@ -126,7 +195,7 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
|
|
|
126
195
|
return <ErrorState headerTitle={t('errorCarePrograms', 'Care programs')} error={error} />;
|
|
127
196
|
}
|
|
128
197
|
|
|
129
|
-
if (
|
|
198
|
+
if (eligibleCarePrograms.length === 0) {
|
|
130
199
|
return <EmptyState headerTitle={t('careProgram', 'Care program')} displayText={t('careProgram', 'care program')} />;
|
|
131
200
|
}
|
|
132
201
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
@use '@carbon/
|
|
2
|
-
@use '@carbon/
|
|
1
|
+
@use '@carbon/type';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
3
|
@use '@carbon/colors';
|
|
4
|
-
|
|
5
4
|
.careProgramButtonContainer {
|
|
6
5
|
display: flex;
|
|
7
6
|
justify-content: space-between;
|
|
@@ -12,3 +11,45 @@
|
|
|
12
11
|
.container {
|
|
13
12
|
border: 1px solid colors.$cool-gray-20;
|
|
14
13
|
}
|
|
14
|
+
|
|
15
|
+
.heading {
|
|
16
|
+
@include type.type-style('heading-compact-01');
|
|
17
|
+
margin: layout.$spacing-05 0 layout.$spacing-05;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.form {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 100%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.grid {
|
|
29
|
+
margin: layout.$spacing-05 layout.$spacing-05;
|
|
30
|
+
padding: layout.$spacing-05 0 0 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.button {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-content: flex-start;
|
|
36
|
+
align-items: baseline;
|
|
37
|
+
min-width: 50%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.buttonSet {
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
width: 100%;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sectionHeader {
|
|
47
|
+
@include type.type-style('heading-02');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.datePickerInput span,
|
|
51
|
+
.datePickerInput div,
|
|
52
|
+
.datePickerInput input,
|
|
53
|
+
.datePickerInput {
|
|
54
|
+
min-width: 100%;
|
|
55
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
import styles from './care-programs.scss';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
ButtonSet,
|
|
6
|
+
Column,
|
|
7
|
+
DatePicker,
|
|
8
|
+
DatePickerInput,
|
|
9
|
+
FormLabel,
|
|
10
|
+
InlineLoading,
|
|
11
|
+
Stack,
|
|
12
|
+
TextInput,
|
|
13
|
+
} from '@carbon/react';
|
|
14
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
15
|
+
import { DefaultWorkspaceProps, ErrorState, LocationPicker, showSnackbar, useSession } from '@openmrs/esm-framework';
|
|
16
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
17
|
+
import { useTranslation } from 'react-i18next';
|
|
18
|
+
import {
|
|
19
|
+
createProgramEnrollment,
|
|
20
|
+
Enrollement,
|
|
21
|
+
ProgramFormData,
|
|
22
|
+
ProgramFormSchema,
|
|
23
|
+
updateProgramEnrollment,
|
|
24
|
+
useProgramDetail,
|
|
25
|
+
} from './care-program.resource';
|
|
26
|
+
|
|
27
|
+
type ProgramFormProps = DefaultWorkspaceProps & {
|
|
28
|
+
enrollment?: Enrollement;
|
|
29
|
+
patientUuid: string;
|
|
30
|
+
programUuid: string;
|
|
31
|
+
onSubmitSuccess?: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ProgramForm: FC<ProgramFormProps> = ({
|
|
35
|
+
patientUuid,
|
|
36
|
+
programUuid,
|
|
37
|
+
enrollment,
|
|
38
|
+
closeWorkspace,
|
|
39
|
+
onSubmitSuccess,
|
|
40
|
+
}) => {
|
|
41
|
+
const getLocationUuid = () => {
|
|
42
|
+
if (!enrollment?.location?.uuid && session?.sessionLocation?.uuid) {
|
|
43
|
+
return session?.sessionLocation?.uuid;
|
|
44
|
+
}
|
|
45
|
+
return enrollment?.location?.uuid ?? null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const session = useSession();
|
|
49
|
+
const { t } = useTranslation();
|
|
50
|
+
const { isLoading: isLoadingProgram, error: programError, program } = useProgramDetail(programUuid);
|
|
51
|
+
const form = useForm<ProgramFormData>({
|
|
52
|
+
resolver: zodResolver(ProgramFormSchema),
|
|
53
|
+
values: {
|
|
54
|
+
dateEnrolled: new Date(),
|
|
55
|
+
location: getLocationUuid() ?? '',
|
|
56
|
+
dateCompleted: enrollment?.dateCompleted ? new Date(enrollment.dateCompleted) : undefined,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const onSubmit = async (data: ProgramFormData) => {
|
|
61
|
+
const abortController = new AbortController();
|
|
62
|
+
try {
|
|
63
|
+
if (enrollment) {
|
|
64
|
+
await updateProgramEnrollment(enrollment.uuid, data, abortController);
|
|
65
|
+
} else {
|
|
66
|
+
await createProgramEnrollment(program, patientUuid, data, abortController);
|
|
67
|
+
}
|
|
68
|
+
showSnackbar({
|
|
69
|
+
kind: 'success',
|
|
70
|
+
title: 'Success',
|
|
71
|
+
subtitle: t('programEnrollmentSuccessful', 'Program enrollment successful'),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
closeWorkspace();
|
|
75
|
+
onSubmitSuccess?.();
|
|
76
|
+
} catch (e) {
|
|
77
|
+
showSnackbar({ kind: 'error', title: 'Error', subtitle: e?.message });
|
|
78
|
+
console.error(e);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
if (isLoadingProgram) {
|
|
82
|
+
return <InlineLoading />;
|
|
83
|
+
}
|
|
84
|
+
if (programError) {
|
|
85
|
+
return <ErrorState headerTitle={t('error', 'Error')} error={programError} />;
|
|
86
|
+
}
|
|
87
|
+
return (
|
|
88
|
+
<form className={styles.form} onSubmit={form.handleSubmit(onSubmit)}>
|
|
89
|
+
<Stack gap={4} className={styles.grid}>
|
|
90
|
+
<Column>
|
|
91
|
+
<TextInput
|
|
92
|
+
readOnly
|
|
93
|
+
value={program?.name}
|
|
94
|
+
title={t('program', 'Program')}
|
|
95
|
+
id={'program'}
|
|
96
|
+
labelText={t('program', 'Program')}
|
|
97
|
+
/>
|
|
98
|
+
</Column>
|
|
99
|
+
<Column>
|
|
100
|
+
<Controller
|
|
101
|
+
control={form.control}
|
|
102
|
+
name="dateEnrolled"
|
|
103
|
+
render={({ field, fieldState: { error } }) => (
|
|
104
|
+
<DatePicker
|
|
105
|
+
className={styles.datePickerInput}
|
|
106
|
+
dateFormat="d/m/Y"
|
|
107
|
+
datePickerType="single"
|
|
108
|
+
value={field.value}
|
|
109
|
+
onChange={(dates) => field.onChange(dates?.[0] ?? undefined)}
|
|
110
|
+
invalid={!!error?.message}
|
|
111
|
+
invalidText={error?.message}>
|
|
112
|
+
<DatePickerInput
|
|
113
|
+
id={`startdate-input`}
|
|
114
|
+
invalid={!!error?.message}
|
|
115
|
+
invalidText={error?.message}
|
|
116
|
+
placeholder="mm/dd/yyyy"
|
|
117
|
+
labelText={t('startDate', 'Start Date')}
|
|
118
|
+
size="lg"
|
|
119
|
+
/>
|
|
120
|
+
</DatePicker>
|
|
121
|
+
)}
|
|
122
|
+
/>
|
|
123
|
+
</Column>
|
|
124
|
+
<Column>
|
|
125
|
+
<Controller
|
|
126
|
+
control={form.control}
|
|
127
|
+
name="dateCompleted"
|
|
128
|
+
render={({ field, fieldState: { error } }) => (
|
|
129
|
+
<DatePicker
|
|
130
|
+
className={styles.datePickerInput}
|
|
131
|
+
dateFormat="d/m/Y"
|
|
132
|
+
value={field.value}
|
|
133
|
+
datePickerType="single"
|
|
134
|
+
onChange={(dates) => field.onChange(dates?.[0] ?? undefined)}
|
|
135
|
+
invalid={!!error?.message}
|
|
136
|
+
invalidText={error?.message}>
|
|
137
|
+
<DatePickerInput
|
|
138
|
+
id="endDate"
|
|
139
|
+
invalid={!!error?.message}
|
|
140
|
+
invalidText={error?.message}
|
|
141
|
+
placeholder="mm/dd/yyyy"
|
|
142
|
+
labelText={t('endDate', 'End Date')}
|
|
143
|
+
size="lg"
|
|
144
|
+
/>
|
|
145
|
+
</DatePicker>
|
|
146
|
+
)}
|
|
147
|
+
/>
|
|
148
|
+
</Column>
|
|
149
|
+
<Column>
|
|
150
|
+
<Controller
|
|
151
|
+
control={form.control}
|
|
152
|
+
name="location"
|
|
153
|
+
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
|
154
|
+
<React.Fragment>
|
|
155
|
+
<FormLabel className={`${styles.locationLabel} cds--label`}>
|
|
156
|
+
{t('enrollmentLocation', 'Enrollment location')}
|
|
157
|
+
</FormLabel>
|
|
158
|
+
<LocationPicker
|
|
159
|
+
selectedLocationUuid={value}
|
|
160
|
+
defaultLocationUuid={session?.sessionLocation?.uuid}
|
|
161
|
+
locationTag="Login Location"
|
|
162
|
+
onChange={(locationUuid) => onChange(locationUuid)}
|
|
163
|
+
/>
|
|
164
|
+
</React.Fragment>
|
|
165
|
+
)}
|
|
166
|
+
/>
|
|
167
|
+
</Column>
|
|
168
|
+
</Stack>
|
|
169
|
+
<ButtonSet className={styles.buttonSet}>
|
|
170
|
+
<Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
|
|
171
|
+
{t('discard', 'Discard')}
|
|
172
|
+
</Button>
|
|
173
|
+
<Button className={styles.button} kind="primary" type="submit" disabled={form.formState.isSubmitting}>
|
|
174
|
+
{t('submit', 'Submit')}
|
|
175
|
+
</Button>
|
|
176
|
+
</ButtonSet>
|
|
177
|
+
</form>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export default ProgramForm;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import useSWR from 'swr';
|
|
2
2
|
import { openmrsFetch } from '@openmrs/esm-framework';
|
|
3
|
+
import { useMemo } from 'react';
|
|
3
4
|
|
|
4
5
|
export type PatientCarePrograms = {
|
|
5
6
|
uuid: string;
|
|
@@ -20,8 +21,18 @@ export const useCarePrograms = (patientUuid: string) => {
|
|
|
20
21
|
mutate: mutateEligiblePrograms,
|
|
21
22
|
} = useSWR<{ data: Array<PatientCarePrograms> }>(url, openmrsFetch);
|
|
22
23
|
|
|
24
|
+
const eligibleCarePrograms = useMemo(
|
|
25
|
+
() => data?.data?.filter((careProgram) => careProgram.enrollmentStatus !== 'active') ?? [],
|
|
26
|
+
[data],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const activeCarePrograms = useMemo(
|
|
30
|
+
() => data?.data?.filter((careProgram) => careProgram.enrollmentStatus !== 'active') ?? [],
|
|
31
|
+
[data],
|
|
32
|
+
);
|
|
23
33
|
return {
|
|
24
|
-
|
|
34
|
+
eligibleCarePrograms,
|
|
35
|
+
activeCarePrograms,
|
|
25
36
|
error,
|
|
26
37
|
isLoading,
|
|
27
38
|
isValidating,
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import PatientSummary from './patient-summary/patient-summary.component';
|
|
|
11
11
|
import DispensingPatientVitals from './dispensing-patient-details/patient-vitals.component';
|
|
12
12
|
import PatientDischargeSideRailIcon from './patient-discharge/discharge-workspace-siderail.component';
|
|
13
13
|
import PatientDischargeWorkspace from './patient-discharge/patient-discharge.workspace';
|
|
14
|
+
import ProgramForm from './care-programs/program.workspace';
|
|
14
15
|
|
|
15
16
|
const moduleName = '@kenyaemr/esm-care-panel-app';
|
|
16
17
|
|
|
@@ -52,3 +53,4 @@ export const patientDischargeWorkspace = getAsyncLifecycle(
|
|
|
52
53
|
() => import('./patient-discharge/patient-discharge.workspace'),
|
|
53
54
|
options,
|
|
54
55
|
);
|
|
56
|
+
export const mchProgramForm = getSyncLifecycle(ProgramForm, options);
|
package/src/routes.json
CHANGED
package/translations/am.json
CHANGED