@openmrs/esm-patient-programs-app 3.2.1-pre.180 → 3.2.1-pre.203

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.
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { Button } from 'carbon-components-react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import TaskAdd20 from '@carbon/icons-react/es/task--add/20';
5
+ import { formEntrySub, launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
6
+ import { ConfigurableProgram } from '../../types';
7
+
8
+ interface ProgramActionButton {
9
+ enrollment: ConfigurableProgram;
10
+ }
11
+
12
+ const ProgramActionButton: React.FC<ProgramActionButton> = ({ enrollment }) => {
13
+ const { t } = useTranslation();
14
+ const formUuid =
15
+ enrollment.enrollmentStatus === 'eligible' ? enrollment.enrollmentFormUuid : enrollment.discontinuationFormUuid;
16
+ const launchEnrollmentForm = (enrollmentStatus: string) => {
17
+ formEntrySub.next({ formUuid, encounterUuid: '' });
18
+ launchPatientWorkspace('patient-form-entry-workspace', {
19
+ workspaceTitle: `${enrollment?.display} ${enrollmentStatus} Form`,
20
+ });
21
+ };
22
+
23
+ if (enrollment.enrollmentStatus === 'eligible') {
24
+ return (
25
+ <Button
26
+ onClick={() => launchEnrollmentForm(t('enrollment', 'Enrollment'))}
27
+ renderIcon={TaskAdd20}
28
+ kind="tertiary"
29
+ size="small"
30
+ >
31
+ {t('enroll', 'Enroll')}
32
+ </Button>
33
+ );
34
+ }
35
+ return (
36
+ <Button
37
+ onClick={() => launchEnrollmentForm(t('discontinue', 'Discontinue'))}
38
+ renderIcon={TaskAdd20}
39
+ kind="danger--ghost"
40
+ size="small"
41
+ >
42
+ {t('discontinue', 'Discontinue')}
43
+ </Button>
44
+ );
45
+ };
46
+
47
+ export default ProgramActionButton;
@@ -7,7 +7,7 @@ import map from 'lodash-es/map';
7
7
  import styles from './programs-detailed-summary.scss';
8
8
  import { CardHeader, EmptyState, ErrorState, launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
9
9
  import { useTranslation } from 'react-i18next';
10
- import { useAvailablePrograms, useEnrollments } from './programs.resource';
10
+ import { useAvailablePrograms, useEnrollments, usePrograms } from './programs.resource';
11
11
  import {
12
12
  Button,
13
13
  DataTable,
@@ -24,7 +24,11 @@ import {
24
24
  DataTableRow,
25
25
  InlineNotification,
26
26
  } from 'carbon-components-react';
27
- import { formatDate, formatDatetime } from '@openmrs/esm-framework';
27
+ import { formatDate, formatDatetime, useConfig, usePagination } from '@openmrs/esm-framework';
28
+ import { ConfigObject } from '../config-schema';
29
+ import { ConfigurableProgram } from '../types';
30
+ import capitalize from 'lodash-es/capitalize';
31
+ import ProgramActionButton from './program-action-button/program-action-button.component';
28
32
 
29
33
  interface ProgramsDetailedSummaryProps {
30
34
  patientUuid: string;
@@ -34,13 +38,18 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
34
38
  const { t } = useTranslation();
35
39
  const displayText = t('programEnrollments', 'Program enrollments');
36
40
  const headerTitle = t('carePrograms', 'Care Programs');
41
+ const config = useConfig() as ConfigObject;
42
+ const programsCount = 5;
43
+ const isConfigurable = config.customUrl ? true : false;
37
44
 
38
- const { data: enrollments, isError, isLoading, isValidating } = useEnrollments(patientUuid);
39
- const { data: availablePrograms } = useAvailablePrograms();
40
- const eligiblePrograms = filter(
41
- availablePrograms,
42
- (program) => !includes(map(enrollments, 'program.uuid'), program.uuid),
43
- );
45
+ const { enrollments, isLoading, isError, isValidating, availablePrograms, eligiblePrograms, configurablePrograms } =
46
+ usePrograms(patientUuid);
47
+
48
+ const {
49
+ results: paginatedEnrollments,
50
+ goTo,
51
+ currentPage,
52
+ } = usePagination(isConfigurable ? configurablePrograms : enrollments ?? [], programsCount);
44
53
 
45
54
  const tableHeaders: Array<DataTableHeader> = React.useMemo(
46
55
  () => [
@@ -68,20 +77,24 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
68
77
  [t],
69
78
  );
70
79
 
71
- const tableRows: Array<DataTableRow> = React.useMemo(() => {
72
- return enrollments?.map((program) => {
73
- return {
74
- id: program.uuid,
75
- display: program.display,
76
- location: program.location?.display,
77
- dateEnrolled: formatDatetime(new Date(program.dateEnrolled)),
78
- status: program.dateCompleted
79
- ? `${t('completedOn', 'Completed On')} ${formatDate(new Date(program.dateCompleted))}`
80
- : t('active', 'Active'),
81
- actions: <ProgramEditButton programEnrollmentId={program.uuid} />,
82
- };
83
- });
84
- }, [enrollments, t]);
80
+ const tableRows = React.useMemo(() => {
81
+ return paginatedEnrollments?.map((enrollment: ConfigurableProgram) => ({
82
+ id: enrollment.uuid,
83
+ display: enrollment.display,
84
+ location: enrollment.location?.display,
85
+ dateEnrolled: enrollment.dateEnrolled ? formatDatetime(new Date(enrollment.dateEnrolled)) : '--',
86
+ status: isConfigurable
87
+ ? capitalize(enrollment.enrollmentStatus)
88
+ : enrollment.dateCompleted
89
+ ? `${t('completedOn', 'Completed On')} ${formatDate(new Date(enrollment.dateCompleted))}`
90
+ : t('active', 'Active'),
91
+ actions: isConfigurable ? (
92
+ <ProgramActionButton enrollment={enrollment} />
93
+ ) : (
94
+ <ProgramEditButton programEnrollmentId={enrollment.uuid} />
95
+ ),
96
+ }));
97
+ }, [isConfigurable, paginatedEnrollments, t]);
85
98
 
86
99
  const launchProgramsForm = React.useCallback(() => launchPatientWorkspace('programs-form-workspace'), []);
87
100
 
@@ -156,7 +169,7 @@ interface ProgramEditButtonProps {
156
169
  function ProgramEditButton({ programEnrollmentId }: ProgramEditButtonProps) {
157
170
  const launchEditProgramsForm = React.useCallback(
158
171
  () => launchPatientWorkspace('programs-form-workspace', { programEnrollmentId }),
159
- [],
172
+ [programEnrollmentId],
160
173
  );
161
174
 
162
175
  return (
@@ -146,7 +146,17 @@ const ProgramsForm: React.FC<ProgramsFormProps> = ({ closeWorkspace, patientUuid
146
146
  sub.unsubscribe();
147
147
  };
148
148
  },
149
- [closeWorkspace, completionDate, enrollmentDate, mutate, patientUuid, selectedProgram, t, userLocation],
149
+ [
150
+ closeWorkspace,
151
+ completionDate,
152
+ enrollmentDate,
153
+ mutate,
154
+ patientUuid,
155
+ selectedProgram,
156
+ t,
157
+ userLocation,
158
+ currentEnrollment,
159
+ ],
150
160
  );
151
161
 
152
162
  return (
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import Add16 from '@carbon/icons-react/es/add/16';
3
3
  import styles from './programs-overview.scss';
4
- import { formatDate, formatDatetime, usePagination } from '@openmrs/esm-framework';
4
+ import { formatDate, formatDatetime, useConfig, usePagination } from '@openmrs/esm-framework';
5
5
  import {
6
6
  DataTable,
7
7
  DataTableSkeleton,
@@ -16,9 +16,6 @@ import {
16
16
  TableRow,
17
17
  InlineNotification,
18
18
  } from 'carbon-components-react';
19
- import filter from 'lodash-es/filter';
20
- import includes from 'lodash-es/includes';
21
- import map from 'lodash-es/map';
22
19
  import {
23
20
  CardHeader,
24
21
  EmptyState,
@@ -27,7 +24,11 @@ import {
27
24
  launchPatientWorkspace,
28
25
  } from '@openmrs/esm-patient-common-lib';
29
26
  import { useTranslation } from 'react-i18next';
30
- import { useAvailablePrograms, useEnrollments } from './programs.resource';
27
+ import { usePrograms } from './programs.resource';
28
+ import { ConfigObject } from '../config-schema';
29
+ import { capitalize } from 'lodash';
30
+ import ProgramActionButton from './program-action-button/program-action-button.component';
31
+ import { ConfigurableProgram } from '../types';
31
32
 
32
33
  interface ProgramsOverviewProps {
33
34
  basePath: string;
@@ -36,23 +37,30 @@ interface ProgramsOverviewProps {
36
37
 
37
38
  const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ basePath, patientUuid }) => {
38
39
  const programsCount = 5;
40
+ const config = useConfig() as ConfigObject;
39
41
  const { t } = useTranslation();
40
42
  const displayText = t('programs', 'Program enrollments');
41
43
  const headerTitle = t('carePrograms', 'Care Programs');
42
44
  const urlLabel = t('seeAll', 'See all');
43
45
  const pageUrl = window.spaBase + basePath + '/programs';
46
+ const isConfigurable = config.customUrl ? true : false;
44
47
 
45
- const { data: enrollments, isError, isLoading, isValidating } = useEnrollments(patientUuid);
46
- const activeEnrollments = enrollments?.filter((enrollment) => !enrollment.dateCompleted);
47
-
48
- const { data: availablePrograms } = useAvailablePrograms();
49
-
50
- const eligiblePrograms = filter(
48
+ const {
49
+ enrollments,
50
+ isLoading,
51
+ isError,
52
+ activeEnrollments,
53
+ isValidating,
51
54
  availablePrograms,
52
- (program) => !includes(map(enrollments, 'program.uuid'), program.uuid),
53
- );
55
+ eligiblePrograms,
56
+ configurablePrograms,
57
+ } = usePrograms(patientUuid);
54
58
 
55
- const { results: paginatedEnrollments, goTo, currentPage } = usePagination(enrollments ?? [], programsCount);
59
+ const {
60
+ results: paginatedEnrollments,
61
+ goTo,
62
+ currentPage,
63
+ } = usePagination(isConfigurable ? configurablePrograms : enrollments ?? [], programsCount);
56
64
 
57
65
  const launchProgramsForm = React.useCallback(() => launchPatientWorkspace('programs-form-workspace'), []);
58
66
 
@@ -73,23 +81,30 @@ const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ basePath, patientUu
73
81
  key: 'status',
74
82
  header: t('status', 'Status'),
75
83
  },
84
+ {
85
+ key: 'actions',
86
+ header: t('actions', 'Actions'),
87
+ },
76
88
  ];
77
89
 
78
90
  const tableRows = React.useMemo(() => {
79
- return paginatedEnrollments?.map((enrollment) => ({
91
+ return paginatedEnrollments?.map((enrollment: ConfigurableProgram) => ({
80
92
  id: enrollment.uuid,
81
93
  display: enrollment.display,
82
94
  location: enrollment.location?.display,
83
- dateEnrolled: formatDatetime(new Date(enrollment.dateEnrolled)),
84
- status: enrollment.dateCompleted
95
+ dateEnrolled: enrollment.dateEnrolled ? formatDatetime(new Date(enrollment.dateEnrolled)) : '--',
96
+ status: isConfigurable
97
+ ? capitalize(enrollment.enrollmentStatus)
98
+ : enrollment.dateCompleted
85
99
  ? `${t('completedOn', 'Completed On')} ${formatDate(new Date(enrollment.dateCompleted))}`
86
100
  : t('active', 'Active'),
101
+ actions: <ProgramActionButton enrollment={enrollment} />,
87
102
  }));
88
- }, [paginatedEnrollments, t]);
103
+ }, [isConfigurable, paginatedEnrollments, t]);
89
104
 
90
105
  if (isLoading) return <DataTableSkeleton role="progressbar" />;
91
106
  if (isError) return <ErrorState error={isError} headerTitle={headerTitle} />;
92
- if (activeEnrollments?.length) {
107
+ if (isConfigurable ? configurablePrograms.length : activeEnrollments?.length) {
93
108
  return (
94
109
  <div className={styles.widgetCard}>
95
110
  <CardHeader title={headerTitle}>
@@ -114,7 +129,12 @@ const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ basePath, patientUu
114
129
  title={t('fullyEnrolled', 'Enrolled in all programs')}
115
130
  />
116
131
  )}
117
- <DataTable rows={tableRows} headers={tableHeaders} isSortable={true} size="short">
132
+ <DataTable
133
+ rows={tableRows}
134
+ headers={isConfigurable ? tableHeaders : tableHeaders.filter((header) => header.key !== 'actions')}
135
+ isSortable={true}
136
+ size="short"
137
+ >
118
138
  {({ rows, headers, getHeaderProps, getTableProps }) => (
119
139
  <Table {...getTableProps()} useZebraStyles>
120
140
  <TableHead>
@@ -150,7 +170,7 @@ const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ basePath, patientUu
150
170
  onPageNumberChange={({ page }) => goTo(page)}
151
171
  pageNumber={currentPage}
152
172
  pageSize={programsCount}
153
- totalItems={enrollments.length}
173
+ totalItems={isConfigurable ? configurablePrograms.length : enrollments?.length}
154
174
  dashboardLinkUrl={pageUrl}
155
175
  dashboardLinkLabel={urlLabel}
156
176
  />
@@ -1,8 +1,12 @@
1
1
  import useSWR from 'swr';
2
- import { map } from 'rxjs/operators';
3
- import { openmrsFetch, openmrsObservableFetch } from '@openmrs/esm-framework';
4
- import { PatientProgram, Program, ProgramsFetchResponse } from '../types';
2
+ import { map as rxjsMap } from 'rxjs/operators';
3
+ import { openmrsFetch, openmrsObservableFetch, useConfig } from '@openmrs/esm-framework';
4
+ import { ConfigurableProgram, PatientProgram, Program, ProgramsFetchResponse } from '../types';
5
5
  import uniqBy from 'lodash-es/uniqBy';
6
+ import filter from 'lodash-es/filter';
7
+ import includes from 'lodash-es/includes';
8
+ import map from 'lodash-es/map';
9
+ import { ConfigObject } from '../config-schema';
6
10
 
7
11
  export const customRepresentation = `custom:(uuid,display,program,dateEnrolled,dateCompleted,location:(uuid,display))`;
8
12
 
@@ -17,30 +21,41 @@ export function useEnrollments(patientUuid: string) {
17
21
  ? data?.data.results.sort((a, b) => (b.dateEnrolled > a.dateEnrolled ? 1 : -1))
18
22
  : null;
19
23
 
24
+ const activeEnrollments = formattedEnrollments?.filter((enrollment) => !enrollment.dateCompleted);
25
+
20
26
  return {
21
27
  data: data ? uniqBy(formattedEnrollments, (program) => program?.program?.uuid) : null,
22
28
  isError: error,
23
29
  isLoading: !data && !error,
24
30
  isValidating,
31
+ activeEnrollments,
25
32
  };
26
33
  }
27
34
 
28
- export function useAvailablePrograms() {
35
+ export function useAvailablePrograms(enrollments?: Array<PatientProgram>) {
29
36
  const { data, error } = useSWR<{ data: { results: Array<Program> } }, Error>(
30
37
  `/ws/rest/v1/program?v=custom:(uuid,display,allWorkflows,concept:(uuid,display))`,
31
38
  openmrsFetch,
32
39
  );
33
40
 
41
+ const availablePrograms = data?.data?.results ?? null;
42
+
43
+ const eligiblePrograms = filter(
44
+ availablePrograms,
45
+ (program) => !includes(map(enrollments, 'program.uuid'), program.uuid),
46
+ );
47
+
34
48
  return {
35
- data: data?.data?.results?.length ? data.data.results : null,
49
+ data: availablePrograms,
36
50
  isError: error,
37
51
  isLoading: !data && !error,
52
+ eligiblePrograms,
38
53
  };
39
54
  }
40
55
 
41
56
  export function getPatientProgramByUuid(programUuid: string) {
42
57
  return openmrsObservableFetch<PatientProgram>(`/ws/rest/v1/programenrollment/${programUuid}`).pipe(
43
- map(({ data }) => data),
58
+ rxjsMap(({ data }) => data),
44
59
  );
45
60
  }
46
61
 
@@ -73,3 +88,43 @@ export function updateProgramEnrollment(programEnrollmentUuid: string, payload,
73
88
  signal: abortController.signal,
74
89
  });
75
90
  }
91
+
92
+ export const useConfigurableProgram = (patientUuid: string) => {
93
+ const { customUrl } = useConfig() as ConfigObject;
94
+ const { data, error } = useSWR<{ data: Array<ConfigurableProgram> }>(
95
+ customUrl ? `${customUrl}${patientUuid}` : null,
96
+ openmrsFetch,
97
+ );
98
+ const configurablePrograms = data?.data ?? [];
99
+ return {
100
+ configurablePrograms,
101
+ isLoading: !data && !error,
102
+ error: error,
103
+ };
104
+ };
105
+
106
+ export const usePrograms = (patientUuid: string) => {
107
+ const { customUrl } = useConfig() as ConfigObject;
108
+ const {
109
+ data: enrollments,
110
+ isError: enrollError,
111
+ isLoading: enrolLoading,
112
+ isValidating,
113
+ activeEnrollments,
114
+ } = useEnrollments(patientUuid);
115
+ const { data: availablePrograms, eligiblePrograms } = useAvailablePrograms(enrollments);
116
+ const { configurablePrograms, isLoading: configLoading, error: configError } = useConfigurableProgram(patientUuid);
117
+
118
+ const status = customUrl
119
+ ? { isLoading: configLoading, isError: configError }
120
+ : { isLoading: enrolLoading, isError: enrollError };
121
+ return {
122
+ enrollments,
123
+ ...status,
124
+ isValidating,
125
+ activeEnrollments,
126
+ availablePrograms,
127
+ eligiblePrograms,
128
+ configurablePrograms,
129
+ };
130
+ };
@@ -99,3 +99,13 @@ export interface SessionData {
99
99
  retired: false;
100
100
  links: Links;
101
101
  }
102
+
103
+ export interface ConfigurableProgram extends PatientProgram {
104
+ uuid: string;
105
+ display: string;
106
+ enrollmentFormUuid: string;
107
+ discontinuationFormUuid: string;
108
+ enrollmentStatus: string;
109
+ dateEnrolled: string;
110
+ dateCompleted: string;
111
+ }
@@ -10,6 +10,9 @@
10
10
  "completedOn": "Completed on",
11
11
  "dateCompleted": "Date completed",
12
12
  "dateEnrolled": "Date enrolled",
13
+ "discontinue": "Discontinue",
14
+ "enroll": "Enroll",
15
+ "enrollment": "Enrollment",
13
16
  "enrollmentLocation": "Enrollment location",
14
17
  "enrollmentNowVisible": "It is now visible in the Programs table",
15
18
  "enrollmentSaved": "Program enrollment saved",