@kenyaemr/esm-care-panel-app 5.4.2-pre.2603 → 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/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.2603"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-care-panel-app",
3
- "version": "5.4.2-pre.2603",
3
+ "version": "5.4.2-pre.2606",
4
4
  "description": "Patient care panels microfrontend for the OpenMRS SPA",
5
5
  "keywords": [
6
6
  "openmrs"
@@ -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 { carePrograms, isLoading, isValidating, error, mutateEligiblePrograms } = useCarePrograms(patientUuid);
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
- carePrograms.map((careProgram) => {
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
- [carePrograms, handleCareProgramClick],
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 (carePrograms.length === 0) {
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/styles/scss/type';
2
- @use '@carbon/styles/scss/spacing';
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
- carePrograms: data?.data?.filter((careProgram) => careProgram.enrollmentStatus !== 'active') ?? [],
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
@@ -83,6 +83,13 @@
83
83
  "canHide": true,
84
84
  "width": "extra-wide",
85
85
  "groups": ["ward-patient"]
86
+ },
87
+
88
+ {
89
+ "name": "mch-program-form-workspace",
90
+ "component": "mchProgramForm",
91
+ "title": "Program Form",
92
+ "type": "workspace"
86
93
  }
87
94
  ]
88
95
  }
@@ -35,6 +35,7 @@
35
35
  "dateOfEntryPoint": "የመግቢያ ነጥብ ቀን",
36
36
  "dateStartedART": "ኤአርቲ የተጀመረበት ቀን",
37
37
  "deathDate": "የሞት ቀን",
38
+ "delete": "Delete",
38
39
  "deleteRegimen": "ስርዓት ሰርዝ",
39
40
  "deleteRegimenModalConfirmationText": "ስርዓቱን መሰረዝ ይፈልጋሉ?",
40
41
  "description": "መግለጫ",