@kenyaemr/esm-care-panel-app 5.4.2-pre.2777 → 5.4.2-pre.2788

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"]},{"name":"mch-program-form-workspace","component":"mchProgramForm","title":"Program Form","type":"workspace"}],"version":"5.4.2-pre.2777"}
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"},{"name":"kvp-peer-linkage-form-workspace","component":"kvpPeerLinkageForm","title":"Program Form","type":"workspace"}],"version":"5.4.2-pre.2788"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-care-panel-app",
3
- "version": "5.4.2-pre.2777",
3
+ "version": "5.4.2-pre.2788",
4
4
  "description": "Patient care panels microfrontend for the OpenMRS SPA",
5
5
  "keywords": [
6
6
  "openmrs"
@@ -1,4 +1,12 @@
1
- import { FetchResponse, launchWorkspace, openmrsFetch, restBaseUrl, showModal } from '@openmrs/esm-framework';
1
+ import {
2
+ FetchResponse,
3
+ launchWorkspace,
4
+ openmrsFetch,
5
+ parseDate,
6
+ restBaseUrl,
7
+ showModal,
8
+ } from '@openmrs/esm-framework';
9
+ import dayjs from 'dayjs';
2
10
  import useSWR, { mutate } from 'swr';
3
11
  import z from 'zod';
4
12
 
@@ -81,6 +89,66 @@ export const useProgramDetail = (programId: string) => {
81
89
  };
82
90
  };
83
91
 
92
+ type FormEncounter = {
93
+ encounter: {
94
+ encounterDatetime: string;
95
+ uuid: string;
96
+ id: number;
97
+ encounterType: string;
98
+ dateCreated: string;
99
+ };
100
+ form: {
101
+ uuid: string;
102
+ name: string;
103
+ };
104
+ };
105
+
106
+ export const usePatientFormEncounter = (patientUuid: string, formUuid: string) => {
107
+ const url = `${restBaseUrl}/kenyaemr/encountersByPatientAndForm?patientUuid=${patientUuid}&formUuid=${formUuid}`;
108
+ const { data, error, isLoading, mutate } = useSWR<FetchResponse<{ results: Array<FormEncounter> }>>(
109
+ url,
110
+ openmrsFetch,
111
+ );
112
+ return {
113
+ formEncounters: (data?.data?.results ?? [])
114
+ .map((data) => ({
115
+ ...data,
116
+ encounter: {
117
+ ...data.encounter,
118
+ encounterDatetime: parseDate(data.encounter.encounterDatetime),
119
+ },
120
+ }))
121
+ .sort((a, b) => dayjs(b.encounter.encounterDatetime).diff(dayjs(a.encounter.encounterDatetime))),
122
+ error,
123
+ isLoading,
124
+ mutate,
125
+ };
126
+ };
127
+
128
+ export const useFormsFilled = (patientUuid: string, formUuids: Array<string> = []) => {
129
+ const url = `${restBaseUrl}/kenyaemr/encountersByPatientAndForm?patientUuid=${patientUuid}&formUuid=${formUuids.join(
130
+ ',',
131
+ )}`;
132
+
133
+ const { data, error, mutate, isLoading } = useSWR(url, async (uri) => {
134
+ const tasks = await Promise.allSettled(
135
+ formUuids.map((formUuid) =>
136
+ openmrsFetch<{ results: Array<FormEncounter> }>(
137
+ `${restBaseUrl}/kenyaemr/encountersByPatientAndForm?patientUuid=${patientUuid}&formUuid=${formUuid}`,
138
+ ),
139
+ ),
140
+ );
141
+ // Return true if all tasks are fullfilled and have related encounter (visit doesnt matter)
142
+ return tasks.every((task) => task.status === 'fulfilled' && task.value.data?.results?.length);
143
+ });
144
+ return {
145
+ formsFilled: data,
146
+ isLoading,
147
+ mutate,
148
+ error,
149
+ };
150
+ };
151
+
84
152
  export const ProgramFormSchema = z.object({
85
153
  dateEnrolled: z.date({ coerce: true }),
86
154
  dateCompleted: z.date({ coerce: true }).optional(),
@@ -16,7 +16,7 @@ import {
16
16
  Tile,
17
17
  } from '@carbon/react';
18
18
  import { Close, DocumentAdd } from '@carbon/react/icons';
19
- import { formatDate, launchWorkspace, restBaseUrl, useLayoutType, useVisit } from '@openmrs/esm-framework';
19
+ import { formatDate, launchWorkspace, restBaseUrl, useConfig, useLayoutType, useVisit } from '@openmrs/esm-framework';
20
20
  import { CardHeader, EmptyState, ErrorState, launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib';
21
21
  import capitalize from 'lodash/capitalize';
22
22
  import React, { useCallback, useMemo } from 'react';
@@ -24,8 +24,10 @@ import { useTranslation } from 'react-i18next';
24
24
  import { mutate } from 'swr';
25
25
  import { PatientCarePrograms, useCarePrograms } from '../hooks/useCarePrograms';
26
26
 
27
+ import { CarePanelConfig } from '../config-schema';
27
28
  import { launchDeleteProgramDialog, launchProgramForm, usePatientEnrolledPrograms } from './care-program.resource';
28
29
  import styles from './care-programs.scss';
30
+ import ProgramFormOverflowMenuItem from './program-form-overflow-menu-item.component';
29
31
  import useCareProgramForms from './useCareProgramForms';
30
32
 
31
33
  type CareProgramsProps = {
@@ -34,8 +36,9 @@ type CareProgramsProps = {
34
36
 
35
37
  const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
36
38
  const { t } = useTranslation();
37
- const { getProgramForms } = useCareProgramForms();
38
- const { currentVisit } = useVisit(patientUuid);
39
+ const { peerCalendarOutreactForm } = useConfig<CarePanelConfig>();
40
+ const { getProgramForms, getProgramEnrollmentForm } = useCareProgramForms();
41
+ const { currentVisit, mutate: mutateVisit } = useVisit(patientUuid);
39
42
  const { eligibleCarePrograms, isLoading, isValidating, error, mutateEligiblePrograms } = useCarePrograms(patientUuid);
40
43
  const {
41
44
  enrollments,
@@ -43,6 +46,7 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
43
46
  error: enrollmentsError,
44
47
  mutate: mutateEnrollments,
45
48
  } = usePatientEnrolledPrograms(patientUuid);
49
+
46
50
  const isTablet = useLayoutType() === 'tablet';
47
51
 
48
52
  const handleMutations = useCallback(() => {
@@ -58,7 +62,9 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
58
62
  undefined,
59
63
  { revalidate: true },
60
64
  );
61
- }, [mutateEligiblePrograms, patientUuid]);
65
+ mutateVisit();
66
+ mutateEnrollments();
67
+ }, [mutateEligiblePrograms, mutateEnrollments, mutateVisit, patientUuid]);
62
68
 
63
69
  const handleCareProgramClick = useCallback(
64
70
  (careProgram: PatientCarePrograms) => {
@@ -72,9 +78,7 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
72
78
  currentVisit
73
79
  ? launchWorkspace('patient-form-entry-workspace', {
74
80
  workspaceTitle: workspaceTitle,
75
- mutateForm: () => {
76
- handleMutations();
77
- },
81
+ mutateForm: handleMutations,
78
82
  formInfo: {
79
83
  encounterUuid: '',
80
84
  formUuid,
@@ -89,8 +93,7 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
89
93
  const rows = useMemo(
90
94
  () => [
91
95
  ...enrollments.map((enrollment) => {
92
- const forms = getProgramForms(enrollment.program.uuid);
93
-
96
+ const forms = getProgramForms(enrollment.program.uuid).filter((form) => !form.isEnrollment);
94
97
  return {
95
98
  id: enrollment.program.uuid,
96
99
  programName: enrollment.program.name,
@@ -99,37 +102,12 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
99
102
  <Tag type="green">Enrolled</Tag>
100
103
  <OverflowMenu aria-label="overflow-menu" flipped>
101
104
  {forms.map((form) => {
102
- const formEncounter = currentVisit?.encounters?.find((en) => en.form?.uuid === form.formUuId);
103
- const areAllDependancyFormsFilled = form.dependancies.every((formUuid) =>
104
- currentVisit?.encounters?.some((en) => en?.form?.uuid === formUuid),
105
- );
106
- const showForm = !form?.dependancies?.length || areAllDependancyFormsFilled;
107
-
108
- if (!showForm) {
109
- return null;
110
- }
111
-
112
105
  return (
113
- <OverflowMenuItem
114
- key={form.formUuId}
115
- itemText={form.formName}
116
- onClick={() => {
117
- if (currentVisit) {
118
- return launchWorkspace('patient-form-entry-workspace', {
119
- workspaceTitle: form.formName,
120
- mutateForm: () => {
121
- mutateEnrollments();
122
- mutateEligiblePrograms();
123
- },
124
- formInfo: {
125
- encounterUuid: formEncounter?.uuid ?? '',
126
- formUuid: form.formUuId,
127
- // additionalProps: { enrollmenrDetails: careProgram.enrollmentDetails ?? {} },
128
- },
129
- });
130
- }
131
- launchStartVisitPrompt();
132
- }}
106
+ <ProgramFormOverflowMenuItem
107
+ form={form}
108
+ visit={currentVisit}
109
+ patientUuid={patientUuid}
110
+ mutate={handleMutations}
133
111
  />
134
112
  );
135
113
  })}
@@ -149,6 +127,8 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
149
127
  };
150
128
  }),
151
129
  ...eligibleCarePrograms.map((careProgram) => {
130
+ const enrollmentForm = getProgramEnrollmentForm(careProgram.uuid);
131
+
152
132
  return {
153
133
  id: `${careProgram.uuid}`,
154
134
  programName: careProgram.display,
@@ -169,12 +149,28 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
169
149
  kind={careProgram.enrollmentStatus == 'active' ? 'danger--ghost' : 'ghost'}
170
150
  iconDescription="Dismiss"
171
151
  // onClick={() => handleCareProgramClick(careProgram)}
172
- onClick={() =>
173
- launchProgramForm(careProgram.uuid, patientUuid, undefined, () => {
174
- mutateEnrollments();
175
- mutateEligiblePrograms();
176
- })
177
- }
152
+ onClick={() => {
153
+ if (!enrollmentForm) {
154
+ return launchProgramForm(careProgram.uuid, patientUuid, undefined, () => {
155
+ mutateEnrollments();
156
+ mutateEligiblePrograms();
157
+ });
158
+ }
159
+ if (currentVisit) {
160
+ if (enrollmentForm) {
161
+ return launchWorkspace('patient-form-entry-workspace', {
162
+ workspaceTitle: enrollmentForm.formName,
163
+ mutateForm: handleMutations,
164
+ formInfo: {
165
+ encounterUuid: '',
166
+ formUuid: enrollmentForm.formUuId,
167
+ // additionalProps: { enrollmenrDetails: careProgram.enrollmentDetails ?? {} },
168
+ },
169
+ });
170
+ }
171
+ }
172
+ launchStartVisitPrompt();
173
+ }}
178
174
  renderIcon={careProgram.enrollmentStatus == 'active' ? Close : DocumentAdd}>
179
175
  {careProgram.enrollmentStatus == 'active' ? 'Discontinue' : 'Enroll'}
180
176
  </Button>
@@ -189,9 +185,11 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
189
185
  getProgramForms,
190
186
  t,
191
187
  currentVisit,
188
+ patientUuid,
189
+ handleMutations,
192
190
  mutateEnrollments,
191
+ getProgramEnrollmentForm,
193
192
  mutateEligiblePrograms,
194
- patientUuid,
195
193
  ],
196
194
  );
197
195
 
@@ -0,0 +1,36 @@
1
+ @use '@carbon/type';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/colors';
4
+
5
+ .form {
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: space-between;
9
+ width: 100%;
10
+ height: 100%;
11
+ }
12
+
13
+ .grid {
14
+ margin: layout.$spacing-05 layout.$spacing-05;
15
+ padding: layout.$spacing-05 0 0 0;
16
+ }
17
+
18
+ .button {
19
+ display: flex;
20
+ align-content: flex-start;
21
+ align-items: baseline;
22
+ min-width: 50%;
23
+ }
24
+
25
+ .buttonSet {
26
+ display: flex;
27
+ justify-content: space-between;
28
+ width: 100%;
29
+ }
30
+
31
+ .datePickerInput span,
32
+ .datePickerInput div,
33
+ .datePickerInput input,
34
+ .datePickerInput {
35
+ min-width: 100%;
36
+ }
@@ -0,0 +1,183 @@
1
+ import z from 'zod';
2
+ import styles from './kvp-patient-peer-form.scss';
3
+ import {
4
+ Button,
5
+ ButtonSet,
6
+ Column,
7
+ ComboBox,
8
+ DatePicker,
9
+ DatePickerInput,
10
+ Form,
11
+ Stack,
12
+ TextInput,
13
+ } from '@carbon/react';
14
+ import { zodResolver } from '@hookform/resolvers/zod';
15
+ import { ConfigObject, DefaultWorkspaceProps, showSnackbar, useConfig } from '@openmrs/esm-framework';
16
+ import toUpper from 'lodash/toUpper';
17
+ import React, { FC } from 'react';
18
+ import { Controller, useForm } from 'react-hook-form';
19
+ import { useTranslation } from 'react-i18next';
20
+ import {
21
+ extractNameString,
22
+ saveRelationship,
23
+ usePatientActivePeerEducator,
24
+ usePeerEducators,
25
+ usePerson,
26
+ } from './kvp-program-actions.resource';
27
+
28
+ export const formSchema = z.object({
29
+ personA: z.string().uuid('Invalid person'),
30
+ personB: z.string().uuid('Invalid person').optional(),
31
+ relationshipType: z.string().uuid(),
32
+ startDate: z.date({ coerce: true }),
33
+ endDate: z.date({ coerce: true }).optional(),
34
+ });
35
+ type FormType = z.infer<typeof formSchema>;
36
+ type KvpPeerLinkageFormProps = DefaultWorkspaceProps & { patientUuid: string };
37
+
38
+ const KvpPeerLinkageForm: FC<KvpPeerLinkageFormProps> = ({ closeWorkspace, patientUuid }) => {
39
+ const { data } = usePeerEducators();
40
+ const { isLoading, person } = usePerson(patientUuid);
41
+ const { caseManagerRelationshipType } = useConfig<ConfigObject>();
42
+ const { mutate } = usePatientActivePeerEducator(patientUuid);
43
+ const { t } = useTranslation();
44
+ const caseManagers =
45
+ data?.data.results.map((manager) => ({
46
+ id: manager.person.uuid,
47
+ text: manager.display,
48
+ })) || [];
49
+
50
+ const form = useForm<FormType>({
51
+ mode: 'all',
52
+ defaultValues: {
53
+ personA: patientUuid,
54
+ personB: '',
55
+ relationshipType: caseManagerRelationshipType,
56
+ },
57
+ resolver: zodResolver(formSchema),
58
+ });
59
+
60
+ const onSubmit = async (values: FormType) => {
61
+ try {
62
+ await saveRelationship(values);
63
+ showSnackbar({
64
+ kind: 'success',
65
+ title: t('success', 'Success'),
66
+ subtitle: t('caseSavedSuccesfully', 'Case saved successfully'),
67
+ isLowContrast: true,
68
+ });
69
+ mutate();
70
+ closeWorkspace();
71
+ } catch (error) {
72
+ showSnackbar({
73
+ kind: 'error',
74
+ title: t('error', 'Error'),
75
+ subtitle: t('errorAddingCase', 'Error Adding patient case'),
76
+ isLowContrast: true,
77
+ });
78
+ }
79
+ };
80
+
81
+ return (
82
+ <Form onSubmit={form.handleSubmit(onSubmit)} className={styles.form}>
83
+ <Stack gap={4} className={styles.grid}>
84
+ <Column>
85
+ <Controller
86
+ control={form.control}
87
+ name="startDate"
88
+ render={({ field, fieldState: { error } }) => (
89
+ <DatePicker
90
+ className={styles.datePickerInput}
91
+ dateFormat="d/m/Y"
92
+ datePickerType="single"
93
+ {...field}
94
+ onChange={([date]) => field.onChange(date)}
95
+ ref={undefined}
96
+ invalid={!!error?.message}
97
+ invalidText={error?.message}>
98
+ <DatePickerInput
99
+ id={`startdate-input`}
100
+ invalid={!!error?.message}
101
+ invalidText={error?.message}
102
+ placeholder="mm/dd/yyyy"
103
+ labelText={t('startDate', 'Start Date')}
104
+ size="lg"
105
+ />
106
+ </DatePicker>
107
+ )}
108
+ />
109
+ </Column>
110
+ <Column>
111
+ <Controller
112
+ control={form.control}
113
+ name="endDate"
114
+ render={({ field, fieldState: { error } }) => (
115
+ <DatePicker
116
+ className={styles.datePickerInput}
117
+ dateFormat="d/m/Y"
118
+ datePickerType="single"
119
+ {...field}
120
+ onChange={([date]) => field.onChange(date)}
121
+ invalid={!!error?.message}
122
+ invalidText={error?.message}>
123
+ <DatePickerInput
124
+ id="endDate"
125
+ invalid={!!error?.message}
126
+ invalidText={error?.message}
127
+ placeholder="mm/dd/yyyy"
128
+ labelText={t('endDate', 'End Date')}
129
+ size="lg"
130
+ />
131
+ </DatePicker>
132
+ )}
133
+ />
134
+ </Column>
135
+ <Column>
136
+ <TextInput id="patient" labelText={t('patient', 'Patient')} value={person?.display} readOnly />
137
+ </Column>
138
+ <Column>
139
+ <Controller
140
+ name="personB"
141
+ control={form.control}
142
+ render={({ field, fieldState }) => {
143
+ return (
144
+ <ComboBox
145
+ id="case_manager_name"
146
+ titleText={t('manager', 'Case Manager')}
147
+ placeholder="Select Case Manager"
148
+ items={caseManagers}
149
+ itemToString={(item) => toUpper(extractNameString(item ? item.text : ''))}
150
+ onChange={(e) => {
151
+ field.onChange(e.selectedItem?.id);
152
+ }}
153
+ selectedItem={caseManagers.find((manager) => manager.id === field.value)}
154
+ invalid={!!fieldState.error}
155
+ invalidText={fieldState.error?.message}
156
+ />
157
+ );
158
+ }}
159
+ />
160
+ </Column>
161
+ <Column>
162
+ <TextInput
163
+ id="relationshipType"
164
+ labelText={t('relationshipType', 'Relationship to patient')}
165
+ value={t('caseManager', 'Case Manager')}
166
+ readOnly
167
+ />
168
+ </Column>
169
+ </Stack>
170
+
171
+ <ButtonSet className={styles.buttonSet}>
172
+ <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
173
+ {t('discard', 'Discard')}
174
+ </Button>
175
+ <Button className={styles.button} kind="primary" type="submit" disabled={form.formState.isSubmitting}>
176
+ {t('submit', 'Submit')}
177
+ </Button>
178
+ </ButtonSet>
179
+ </Form>
180
+ );
181
+ };
182
+
183
+ export default KvpPeerLinkageForm;
@@ -0,0 +1,91 @@
1
+ import { FetchResponse, openmrsFetch, Patient, restBaseUrl, useConfig } from '@openmrs/esm-framework';
2
+ import { CarePanelConfig } from '../config-schema';
3
+ import useSWR from 'swr';
4
+ import { useMemo } from 'react';
5
+ import dayjs from 'dayjs';
6
+ import useSWRImmutable from 'swr/immutable';
7
+
8
+ type Relationship = {
9
+ display: string;
10
+ uuid: string;
11
+ personA: {
12
+ uuid: string;
13
+ display: string;
14
+ };
15
+ personB: {
16
+ uuid: string;
17
+ display: string;
18
+ };
19
+ startDate: string;
20
+ endDate: string;
21
+ relationshipType: {
22
+ uuid: string;
23
+ display: string;
24
+ };
25
+ };
26
+
27
+ interface PeerEducator {
28
+ uuid: string;
29
+ display: string;
30
+ person: {
31
+ uuid: string;
32
+ };
33
+ }
34
+
35
+ export const usePatientActivePeerEducator = (patientUuid: string) => {
36
+ const { peerEducatorRelationshipType } = useConfig<CarePanelConfig>();
37
+ const rep =
38
+ 'custom:(display,uuid,personA:(uuid,display),personB:(uuid,display),startDate,endDate,relationshipType:(uuid,display))';
39
+ const url = `${restBaseUrl}/relationship?person=${patientUuid}&v=${rep}`;
40
+ const { data, error, isLoading, mutate } = useSWR<FetchResponse<{ results: Array<Relationship> }>>(url, openmrsFetch);
41
+ const activePeer = useMemo(
42
+ () =>
43
+ (data?.data?.results ?? []).filter(
44
+ (rel) =>
45
+ rel.relationshipType.uuid === peerEducatorRelationshipType &&
46
+ (!rel.endDate || dayjs(rel.endDate).isAfter(dayjs())),
47
+ ),
48
+ [data, peerEducatorRelationshipType],
49
+ );
50
+ return {
51
+ activePeer,
52
+ isLoading,
53
+ mutate,
54
+ error,
55
+ };
56
+ };
57
+
58
+ export const usePeerEducators = () => {
59
+ const customRepresentation = 'custom:(uuid,display,person:(uuid))';
60
+ const url = `/ws/rest/v1/provider?v=${customRepresentation}`;
61
+ const { data, error } = useSWRImmutable<FetchResponse<{ results: Array<PeerEducator> }>>(url, openmrsFetch);
62
+
63
+ return { data, error };
64
+ };
65
+
66
+ export const saveRelationship = (payload) => {
67
+ const url = `/ws/rest/v1/relationship`;
68
+ return openmrsFetch(url, {
69
+ method: 'POST',
70
+ body: JSON.stringify(payload),
71
+ headers: {
72
+ 'Content-Type': 'application/json',
73
+ },
74
+ });
75
+ };
76
+
77
+ export const usePerson = (uuid: string) => {
78
+ const customRepresentation = `custom:(uuid,display,gender,birthdate,dead,age,deathDate,causeOfDeath:(uuid,display),attributes:(uuid,display,value,attributeType:(uuid,display)))`;
79
+ const url = `${restBaseUrl}/person/${uuid}?v=${customRepresentation}`;
80
+ const { isLoading, error, data } = useSWR<FetchResponse<Patient['person']>>(url, openmrsFetch);
81
+ const person = data?.data;
82
+ return { isLoading, error, person };
83
+ };
84
+
85
+ export function extractNameString(formattedString: string) {
86
+ if (!formattedString) {
87
+ return '';
88
+ }
89
+ const parts = formattedString.split(' - ');
90
+ return parts.length > 1 ? parts[1] : '';
91
+ }
@@ -0,0 +1,70 @@
1
+ import React, { FC } from 'react';
2
+ import { usePatientActivePeerEducator } from './kvp-program-actions.resource';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { InlineLoading, OverflowMenuItem } from '@carbon/react';
5
+ import { launchWorkspace, useConfig, Visit } from '@openmrs/esm-framework';
6
+ import { CarePanelConfig } from '../config-schema';
7
+ import { launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib';
8
+
9
+ type KvpLinkPatientToPeerEducatorProps = {
10
+ patientUuid: string;
11
+ form: CarePanelConfig['careProgramForms'][0]['forms'][0];
12
+ visit?: Visit;
13
+ mutate?: () => void;
14
+ };
15
+ const KvpLinkPatientToPeerEducator: FC<KvpLinkPatientToPeerEducatorProps> = ({
16
+ patientUuid,
17
+ form,
18
+ visit: currentVisit,
19
+ mutate,
20
+ }) => {
21
+ const { activePeer, error, isLoading } = usePatientActivePeerEducator(patientUuid);
22
+ const { hideFilledProgramForm } = useConfig<CarePanelConfig>();
23
+ const { t } = useTranslation();
24
+ const formEncounter = currentVisit?.encounters?.find((en) => en.form?.uuid === form.formUuId);
25
+
26
+ if (isLoading) {
27
+ return <InlineLoading />;
28
+ }
29
+
30
+ if (!activePeer.length) {
31
+ return (
32
+ <OverflowMenuItem
33
+ itemText={t('linkToPeerEducator', 'Link to peer Educator')}
34
+ onClick={() => {
35
+ launchWorkspace('kvp-peer-linkage-form-workspace', {
36
+ workspaceTitle: t('linkPatientToPeerEducator', 'Link Patient to Peer Educator'),
37
+ patientUuid,
38
+ });
39
+ }}
40
+ />
41
+ );
42
+ }
43
+
44
+ if (hideFilledProgramForm && formEncounter) {
45
+ return null;
46
+ }
47
+
48
+ return (
49
+ <OverflowMenuItem
50
+ key={form.formUuId}
51
+ itemText={form.formName}
52
+ onClick={() => {
53
+ if (currentVisit) {
54
+ return launchWorkspace('patient-form-entry-workspace', {
55
+ workspaceTitle: form.formName,
56
+ mutateForm: mutate,
57
+ formInfo: {
58
+ encounterUuid: formEncounter?.uuid ?? '',
59
+ formUuid: form.formUuId,
60
+ // additionalProps: { enrollmenrDetails: careProgram.enrollmentDetails ?? {} },
61
+ },
62
+ });
63
+ }
64
+ launchStartVisitPrompt();
65
+ }}
66
+ />
67
+ );
68
+ };
69
+
70
+ export default KvpLinkPatientToPeerEducator;