@openmrs/esm-patient-programs-app 3.2.1-pre.97 → 4.0.1-pre.221

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.
Files changed (54) hide show
  1. package/.turbo/turbo-build.log +34 -35
  2. package/dist/109.js +1 -0
  3. package/dist/13.js +1 -1
  4. package/dist/145.js +1 -1
  5. package/dist/154.js +2 -0
  6. package/dist/154.js.LICENSE.txt +53 -0
  7. package/dist/238.js +2 -0
  8. package/dist/{707.js.LICENSE.txt → 238.js.LICENSE.txt} +0 -0
  9. package/dist/340.js +1 -0
  10. package/dist/435.js +1 -0
  11. package/dist/461.js +1 -0
  12. package/dist/574.js +1 -1
  13. package/dist/709.js +1 -0
  14. package/dist/784.js +1 -1
  15. package/dist/784.js.LICENSE.txt +2 -7
  16. package/dist/858.js +1 -1
  17. package/dist/858.js.LICENSE.txt +2 -0
  18. package/dist/main.js +1 -0
  19. package/dist/openmrs-esm-patient-programs-app.js +1 -1
  20. package/dist/openmrs-esm-patient-programs-app.js.buildmanifest.json +116 -187
  21. package/dist/openmrs-esm-patient-programs-app.old +1 -1
  22. package/package.json +7 -11
  23. package/src/config-schema.ts +12 -0
  24. package/src/dashboard.meta.tsx +1 -1
  25. package/src/index.ts +3 -2
  26. package/src/programs/program-action-button/program-action-button.component.tsx +47 -0
  27. package/src/programs/programs-detailed-summary.component.tsx +68 -37
  28. package/src/programs/programs-detailed-summary.scss +1 -4
  29. package/src/programs/programs-detailed-summary.test.tsx +26 -7
  30. package/src/programs/programs-form.component.tsx +102 -46
  31. package/src/programs/programs-form.scss +1 -9
  32. package/src/programs/programs-form.test.tsx +113 -110
  33. package/src/programs/programs-overview.component.tsx +59 -45
  34. package/src/programs/programs-overview.scss +1 -3
  35. package/src/programs/programs-overview.test.tsx +6 -7
  36. package/src/programs/programs.resource.tsx +63 -8
  37. package/src/types/index.ts +16 -2
  38. package/translations/en.json +8 -2
  39. package/dist/142.js +0 -1
  40. package/dist/186.js +0 -2
  41. package/dist/186.js.LICENSE.txt +0 -23
  42. package/dist/252.js +0 -2
  43. package/dist/252.js.LICENSE.txt +0 -23
  44. package/dist/336.js +0 -2
  45. package/dist/336.js.LICENSE.txt +0 -20
  46. package/dist/373.js +0 -1
  47. package/dist/402.js +0 -1
  48. package/dist/635.js +0 -2
  49. package/dist/635.js.LICENSE.txt +0 -16
  50. package/dist/692.js +0 -1
  51. package/dist/701.js +0 -1
  52. package/dist/707.js +0 -2
  53. package/dist/76.js +0 -1
  54. package/dist/932.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-patient-programs-app",
3
- "version": "3.2.1-pre.97",
3
+ "version": "4.0.1-pre.221",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Patient programs microfrontend for the OpenMRS SPA",
6
6
  "browser": "dist/openmrs-esm-patient-programs-app.js",
@@ -12,7 +12,7 @@
12
12
  "debug": "npm run serve",
13
13
  "build": "webpack --mode production",
14
14
  "analyze": "webpack --mode=production --env analyze=true",
15
- "lint": "eslint src --ext tsx,ts",
15
+ "lint": "eslint src --ext tsx,ts --fix --max-warnings=0",
16
16
  "typescript": "tsc",
17
17
  "extract-translations": "i18next 'src/**/*.component.tsx' --config ../../tools/i18next-parser.config.js"
18
18
  },
@@ -34,23 +34,19 @@
34
34
  "url": "https://github.com/openmrs/openmrs-esm-patient-chart/issues"
35
35
  },
36
36
  "dependencies": {
37
- "@carbon/charts-react": "^0.49.4",
38
- "@carbon/icons-react": "^10.18.0",
39
- "@openmrs/esm-patient-common-lib": "^3.2.1-pre.97",
40
- "carbon-components-react": "^7.25.0",
37
+ "@carbon/react": "^1.8.0",
38
+ "@openmrs/esm-patient-common-lib": "^4.0.1-pre.221",
41
39
  "d3": "^7.0.3",
42
40
  "lodash-es": "^4.17.15"
43
41
  },
44
42
  "peerDependencies": {
45
43
  "@openmrs/esm-framework": "3.x",
46
44
  "@openmrs/esm-patient-common-lib": "3.x",
47
- "carbon-components": "10.x",
48
- "carbon-icons": "7.x",
49
45
  "dayjs": "1.x",
50
- "react": "16.x",
46
+ "react": "^18.2.0",
51
47
  "react-i18next": "11.x",
52
- "react-router-dom": "5.x",
48
+ "react-router-dom": "6.x",
53
49
  "rxjs": "6.x"
54
50
  },
55
- "gitHead": "d0a4c9a83cc55d8f19fb5febb204b80d69e40f7a"
51
+ "gitHead": "4823a7e6eec21a1a4491a545f1e54ea34b64c2c9"
56
52
  }
@@ -0,0 +1,12 @@
1
+ import { Type } from '@openmrs/esm-framework';
2
+
3
+ export const configSchema = {
4
+ customUrl: {
5
+ _type: Type.String,
6
+ _default: '',
7
+ },
8
+ };
9
+
10
+ export interface ConfigObject {
11
+ customUrl: string;
12
+ }
@@ -1,5 +1,5 @@
1
1
  export const dashboardMeta = {
2
2
  slot: 'patient-chart-programs-dashboard-slot',
3
- config: { columns: 1 },
3
+ columns: 1,
4
4
  title: 'Programs',
5
5
  };
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defineConfigSchema, getAsyncLifecycle, getSyncLifecycle } from '@openmrs/esm-framework';
2
2
  import { createDashboardLink } from '@openmrs/esm-patient-common-lib';
3
+ import { configSchema } from './config-schema';
3
4
  import { dashboardMeta } from './dashboard.meta';
4
5
 
5
6
  const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
@@ -16,7 +17,7 @@ function setupOpenMRS() {
16
17
  moduleName,
17
18
  };
18
19
 
19
- defineConfigSchema(moduleName, {});
20
+ defineConfigSchema(moduleName, configSchema);
20
21
 
21
22
  return {
22
23
  extensions: [
@@ -48,7 +49,7 @@ function setupOpenMRS() {
48
49
  name: 'programs-form-workspace',
49
50
  load: getAsyncLifecycle(() => import('./programs/programs-form.component'), options),
50
51
  meta: {
51
- title: 'Record Program enrollment',
52
+ title: 'Record program enrollment',
52
53
  },
53
54
  },
54
55
  ],
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button } from '@carbon/react';
4
+ import { TaskAdd } from '@carbon/react/icons';
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={(props) => <TaskAdd size={20} {...props} />}
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={(props) => <TaskAdd size={20} {...props} />}
39
+ kind="danger--ghost"
40
+ size="small"
41
+ >
42
+ {t('discontinue', 'Discontinue')}
43
+ </Button>
44
+ );
45
+ };
46
+
47
+ export default ProgramActionButton;
@@ -1,29 +1,29 @@
1
1
  import React from 'react';
2
- import Add16 from '@carbon/icons-react/es/add/16';
3
- import filter from 'lodash-es/filter';
4
- import includes from 'lodash-es/includes';
5
- import map from 'lodash-es/map';
6
- import styles from './programs-detailed-summary.scss';
7
- import { CardHeader, EmptyState, ErrorState, launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
8
2
  import { useTranslation } from 'react-i18next';
9
- import { useAvailablePrograms, useEnrollments } from './programs.resource';
3
+ import capitalize from 'lodash-es/capitalize';
10
4
  import {
11
5
  Button,
12
6
  DataTable,
7
+ DataTableHeader,
8
+ DataTableRow,
13
9
  DataTableSkeleton,
14
10
  InlineLoading,
11
+ InlineNotification,
15
12
  Table,
13
+ TableBody,
16
14
  TableCell,
17
15
  TableContainer,
18
- TableBody,
19
16
  TableHead,
20
17
  TableHeader,
21
18
  TableRow,
22
- DataTableHeader,
23
- DataTableRow,
24
- InlineNotification,
25
- } from 'carbon-components-react';
26
- import { formatDate, formatDatetime } from '@openmrs/esm-framework';
19
+ } from '@carbon/react';
20
+ import { Add, Edit } from '@carbon/react/icons';
21
+ import { formatDate, formatDatetime, useConfig, usePagination, ConfigObject } from '@openmrs/esm-framework';
22
+ import { CardHeader, EmptyState, ErrorState, launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
23
+ import { useAvailablePrograms, useEnrollments, usePrograms } from './programs.resource';
24
+ import { ConfigurableProgram } from '../types';
25
+ import ProgramActionButton from './program-action-button/program-action-button.component';
26
+ import styles from './programs-detailed-summary.scss';
27
27
 
28
28
  interface ProgramsDetailedSummaryProps {
29
29
  patientUuid: string;
@@ -33,13 +33,18 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
33
33
  const { t } = useTranslation();
34
34
  const displayText = t('programEnrollments', 'Program enrollments');
35
35
  const headerTitle = t('carePrograms', 'Care Programs');
36
+ const config = useConfig() as ConfigObject;
37
+ const programsCount = 5;
38
+ const isConfigurable = config.customUrl ? true : false;
36
39
 
37
- const { data: enrollments, isError, isLoading, isValidating } = useEnrollments(patientUuid);
38
- const { data: availablePrograms } = useAvailablePrograms();
39
- const eligiblePrograms = filter(
40
- availablePrograms,
41
- (program) => !includes(map(enrollments, 'program.uuid'), program.uuid),
42
- );
40
+ const { enrollments, isLoading, isError, isValidating, availablePrograms, eligiblePrograms, configurablePrograms } =
41
+ usePrograms(patientUuid);
42
+
43
+ const {
44
+ results: paginatedEnrollments,
45
+ goTo,
46
+ currentPage,
47
+ } = usePagination(isConfigurable ? configurablePrograms : enrollments ?? [], programsCount);
43
48
 
44
49
  const tableHeaders: Array<DataTableHeader> = React.useMemo(
45
50
  () => [
@@ -59,11 +64,15 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
59
64
  key: 'status',
60
65
  header: t('status', 'Status'),
61
66
  },
67
+ {
68
+ key: 'actions',
69
+ header: t('actions', 'Actions'),
70
+ },
62
71
  ],
63
72
  [t],
64
73
  );
65
74
 
66
- const tableRows: Array<DataTableRow> = React.useMemo(() => {
75
+ const tableRows = React.useMemo(() => {
67
76
  return enrollments?.map((program) => {
68
77
  return {
69
78
  id: program.uuid,
@@ -73,6 +82,7 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
73
82
  status: program.dateCompleted
74
83
  ? `${t('completedOn', 'Completed On')} ${formatDate(new Date(program.dateCompleted))}`
75
84
  : t('active', 'Active'),
85
+ actions: <ProgramEditButton programEnrollmentId={program.uuid} />,
76
86
  };
77
87
  });
78
88
  }, [enrollments, t]);
@@ -88,7 +98,7 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
88
98
  <span>{isValidating ? <InlineLoading /> : null}</span>
89
99
  <Button
90
100
  kind="ghost"
91
- renderIcon={Add16}
101
+ renderIcon={(props) => <Add size={16} {...props} />}
92
102
  iconDescription="Add programs"
93
103
  onClick={launchProgramsForm}
94
104
  disabled={availablePrograms?.length && eligiblePrograms?.length === 0}
@@ -96,19 +106,19 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
96
106
  {t('add', 'Add')}
97
107
  </Button>
98
108
  </CardHeader>
99
- <TableContainer>
100
- {availablePrograms?.length && eligiblePrograms?.length === 0 && (
101
- <InlineNotification
102
- style={{ minWidth: '100%', margin: '0rem', padding: '0rem' }}
103
- kind={'info'}
104
- lowContrast
105
- subtitle={t('noEligibleEnrollments', 'There are no more programs left to enroll this patient in')}
106
- title={t('fullyEnrolled', 'Enrolled in all programs')}
107
- />
108
- )}
109
- <DataTable rows={tableRows} headers={tableHeaders} isSortable={true} size="short">
110
- {({ rows, headers, getHeaderProps, getTableProps }) => (
111
- <Table {...getTableProps()} useZebraStyles>
109
+ {availablePrograms?.length && eligiblePrograms?.length === 0 ? (
110
+ <InlineNotification
111
+ style={{ minWidth: '100%', margin: '0rem', padding: '0rem' }}
112
+ kind={'info'}
113
+ lowContrast
114
+ subtitle={t('noEligibleEnrollments', 'There are no more programs left to enroll this patient in')}
115
+ title={t('fullyEnrolled', 'Enrolled in all programs')}
116
+ />
117
+ ) : null}
118
+ <DataTable rows={tableRows} headers={tableHeaders} isSortable size="xs" useZebraStyles>
119
+ {({ rows, headers, getHeaderProps, getTableProps }) => (
120
+ <TableContainer>
121
+ <Table {...getTableProps()}>
112
122
  <TableHead>
113
123
  <TableRow>
114
124
  {headers.map((header) => (
@@ -134,13 +144,34 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
134
144
  ))}
135
145
  </TableBody>
136
146
  </Table>
137
- )}
138
- </DataTable>
139
- </TableContainer>
147
+ </TableContainer>
148
+ )}
149
+ </DataTable>
140
150
  </div>
141
151
  );
142
152
  }
143
153
  return <EmptyState displayText={displayText} headerTitle={headerTitle} launchForm={launchProgramsForm} />;
144
154
  };
145
155
 
156
+ interface ProgramEditButtonProps {
157
+ programEnrollmentId: string;
158
+ }
159
+
160
+ function ProgramEditButton({ programEnrollmentId }: ProgramEditButtonProps) {
161
+ const launchEditProgramsForm = React.useCallback(
162
+ () => launchPatientWorkspace('programs-form-workspace', { programEnrollmentId }),
163
+ [programEnrollmentId],
164
+ );
165
+
166
+ return (
167
+ <Button
168
+ kind="ghost"
169
+ renderIcon={(props) => <Edit size={16} {...props} />}
170
+ iconDescription="Edit Program"
171
+ onClick={launchEditProgramsForm}
172
+ hasIconOnly
173
+ />
174
+ );
175
+ }
176
+
146
177
  export default ProgramsDetailedSummary;
@@ -1,7 +1,4 @@
1
-
2
- @import "~@openmrs/esm-styleguide/src/vars";
3
- @import "~carbon-components/src/globals/scss/vars";
4
- @import "~carbon-components/src/globals/scss/mixins";
1
+ @import '~@openmrs/esm-styleguide/src/vars';
5
2
 
6
3
  .widgetCard {
7
4
  border: 1px solid $ui-03;
@@ -1,12 +1,15 @@
1
1
  import React from 'react';
2
- import { screen } from '@testing-library/react';
2
+ import { screen, within, waitFor } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import { openmrsFetch, usePagination } from '@openmrs/esm-framework';
5
+ import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
6
+ import { mockPatient } from '../../../../__mocks__/patient.mock';
5
7
  import { mockEnrolledProgramsResponse } from '../../../../__mocks__/programs.mock';
6
8
  import { renderWithSwr, waitForLoadingToFinish } from '../../../../tools/test-helpers';
7
- import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
8
9
  import ProgramsDetailedSummary from './programs-detailed-summary.component';
9
10
 
11
+ jest.setTimeout(20000);
12
+
10
13
  const mockOpenmrsFetch = openmrsFetch as jest.Mock;
11
14
  const mockUsePagination = usePagination as jest.Mock;
12
15
 
@@ -70,6 +73,8 @@ describe('ProgramsDetailedSummary ', () => {
70
73
  });
71
74
 
72
75
  it("renders a detailed tabular summary of the patient's program enrollments", async () => {
76
+ const user = userEvent.setup();
77
+
73
78
  mockOpenmrsFetch.mockReturnValueOnce({ data: { results: mockEnrolledProgramsResponse } });
74
79
  mockUsePagination.mockImplementation(() => ({
75
80
  currentPage: 1,
@@ -89,16 +94,30 @@ describe('ProgramsDetailedSummary ', () => {
89
94
 
90
95
  const addButton = screen.getByRole('button', { name: /Add/ });
91
96
  expect(addButton).toBeInTheDocument();
92
- expect(screen.getByRole('row', { name: /hiv care and treatment/i })).toBeInTheDocument();
93
- expect(screen.getByRole('row', { name: /16-Jan-2020/i })).toBeInTheDocument();
94
- expect(screen.getByRole('row', { name: /active$/i })).toBeInTheDocument();
97
+ const row = screen.getByRole('row', { name: /hiv care and treatment/i });
98
+ expect(row).toBeInTheDocument();
99
+ expect(within(row).getByRole('cell', { name: /16-Jan-2020/i })).toBeInTheDocument();
100
+ expect(within(row).getByRole('cell', { name: /active$/i })).toBeInTheDocument();
101
+ const editButton = within(row).getByRole('button', { name: /Edit Program$/i });
102
+ expect(editButton).toBeInTheDocument();
95
103
 
96
104
  // Clicking "Add" launches the programs form in a workspace
97
- userEvent.click(addButton);
105
+ await waitFor(() => user.click(addButton));
106
+
98
107
  expect(launchPatientWorkspace).toHaveBeenCalledWith('programs-form-workspace');
108
+
109
+ // Clicking the edit button launches the edit form in a workspace
110
+ await waitFor(() => user.click(editButton));
111
+
112
+ expect(launchPatientWorkspace).toHaveBeenCalledWith('programs-form-workspace', {
113
+ programEnrollmentId: mockEnrolledProgramsResponse[0].uuid,
114
+ });
99
115
  });
100
116
  });
101
117
 
102
118
  function renderProgramsOverview() {
103
- renderWithSwr(<ProgramsDetailedSummary />);
119
+ const testProps = {
120
+ patientUuid: mockPatient.id,
121
+ };
122
+ renderWithSwr(<ProgramsDetailedSummary {...testProps} />);
104
123
  }
@@ -1,10 +1,10 @@
1
1
  import React, { SyntheticEvent } from 'react';
2
+ import { useTranslation } from 'react-i18next';
2
3
  import dayjs from 'dayjs';
3
4
  import filter from 'lodash-es/filter';
4
5
  import includes from 'lodash-es/includes';
5
6
  import map from 'lodash-es/map';
6
7
  import { useSWRConfig } from 'swr';
7
- import { useTranslation } from 'react-i18next';
8
8
  import {
9
9
  createErrorHandler,
10
10
  showNotification,
@@ -12,27 +12,34 @@ import {
12
12
  useSession,
13
13
  useLocations,
14
14
  useLayoutType,
15
+ parseDate,
15
16
  } from '@openmrs/esm-framework';
16
17
  import {
17
18
  Button,
19
+ ButtonSet,
18
20
  DatePicker,
19
21
  DatePickerInput,
20
- Select,
21
- SelectItem,
22
22
  Form,
23
23
  FormGroup,
24
- ButtonSet,
25
- } from 'carbon-components-react';
24
+ Select,
25
+ SelectItem,
26
+ Stack,
27
+ } from '@carbon/react';
26
28
  import {
27
29
  createProgramEnrollment,
28
30
  useAvailablePrograms,
29
31
  useEnrollments,
30
32
  customRepresentation,
33
+ updateProgramEnrollment,
31
34
  } from './programs.resource';
32
35
  import { DefaultWorkspaceProps } from '@openmrs/esm-patient-common-lib';
33
36
  import styles from './programs-form.scss';
34
37
 
35
- const ProgramsForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace, patientUuid }) => {
38
+ interface ProgramsFormProps extends DefaultWorkspaceProps {
39
+ programEnrollmentId?: string;
40
+ }
41
+
42
+ const ProgramsForm: React.FC<ProgramsFormProps> = ({ closeWorkspace, patientUuid, programEnrollmentId }) => {
36
43
  const { t } = useTranslation();
37
44
  const isTablet = useLayoutType() === 'tablet';
38
45
  const session = useSession();
@@ -41,16 +48,25 @@ const ProgramsForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace, patient
41
48
 
42
49
  const { data: availablePrograms } = useAvailablePrograms();
43
50
  const { data: enrollments } = useEnrollments(patientUuid);
51
+ const currentEnrollment = programEnrollmentId && enrollments.filter((e) => e.uuid == programEnrollmentId)[0];
52
+ const currentProgram = currentEnrollment
53
+ ? {
54
+ display: currentEnrollment.program.name,
55
+ ...currentEnrollment.program,
56
+ }
57
+ : null;
44
58
 
45
- const eligiblePrograms = filter(
46
- availablePrograms,
47
- (program) => !includes(map(enrollments, 'program.uuid'), program.uuid),
59
+ const eligiblePrograms = currentProgram
60
+ ? [currentProgram]
61
+ : filter(availablePrograms, (program) => !includes(map(enrollments, 'program.uuid'), program.uuid));
62
+ const [completionDate, setCompletionDate] = React.useState<Date>(
63
+ currentEnrollment?.dateCompleted ? parseDate(currentEnrollment.dateCompleted) : null,
48
64
  );
49
-
50
- const [completionDate, setCompletionDate] = React.useState(null);
51
- const [enrollmentDate, setEnrollmentDate] = React.useState(new Date());
52
- const [selectedProgram, setSelectedProgram] = React.useState<string>('');
53
- const [userLocation, setUserLocation] = React.useState('');
65
+ const [enrollmentDate, setEnrollmentDate] = React.useState<Date>(
66
+ currentEnrollment?.dateEnrolled ? parseDate(currentEnrollment.dateEnrolled) : new Date(),
67
+ );
68
+ const [selectedProgram, setSelectedProgram] = React.useState<string>(currentEnrollment?.program.uuid ?? '');
69
+ const [userLocation, setUserLocation] = React.useState<string>(currentEnrollment?.location.uuid ?? '');
54
70
 
55
71
  if (!userLocation && session?.sessionLocation?.uuid) {
56
72
  setUserLocation(session?.sessionLocation?.uuid);
@@ -71,43 +87,83 @@ const ProgramsForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace, patient
71
87
  };
72
88
 
73
89
  const abortController = new AbortController();
74
- const sub = createProgramEnrollment(payload, abortController).subscribe(
75
- (response) => {
76
- if (response.status === 201) {
77
- closeWorkspace();
78
-
79
- showToast({
80
- critical: true,
81
- kind: 'success',
82
- description: t('enrollmentNowVisible', 'It is now visible on the Programs page'),
83
- title: t('enrollmentSaved', 'Program enrollment saved'),
84
- });
85
-
86
- mutate(`/ws/rest/v1/programenrollment?patient=${patientUuid}&v=${customRepresentation}`);
87
- }
88
- },
89
- (err) => {
90
- createErrorHandler();
91
-
92
- showNotification({
93
- title: t('programEnrollmentSaveError', 'Error saving program enrollment'),
94
- kind: 'error',
95
- critical: true,
96
- description: err?.message,
97
- });
98
- },
99
- );
90
+ const sub = currentEnrollment
91
+ ? updateProgramEnrollment(currentEnrollment.uuid, payload, abortController).subscribe(
92
+ (response) => {
93
+ if (response.status === 200) {
94
+ closeWorkspace();
95
+
96
+ showToast({
97
+ critical: true,
98
+ kind: 'success',
99
+ description: t(
100
+ 'enrollmentUpdatesNowVisible',
101
+ 'Changes to the program are now visible in the Programs table',
102
+ ),
103
+ title: t('enrollmentUpdated', 'Program enrollment updated'),
104
+ });
105
+
106
+ mutate(`/ws/rest/v1/programenrollment?patient=${patientUuid}&v=${customRepresentation}`);
107
+ }
108
+ },
109
+ (err) => {
110
+ createErrorHandler();
111
+
112
+ showNotification({
113
+ title: t('programEnrollmentSaveError', 'Error saving program enrollment'),
114
+ kind: 'error',
115
+ critical: true,
116
+ description: err?.message,
117
+ });
118
+ },
119
+ )
120
+ : createProgramEnrollment(payload, abortController).subscribe(
121
+ (response) => {
122
+ if (response.status === 201) {
123
+ closeWorkspace();
124
+
125
+ showToast({
126
+ critical: true,
127
+ kind: 'success',
128
+ description: t('enrollmentNowVisible', 'It is now visible in the Programs table'),
129
+ title: t('enrollmentSaved', 'Program enrollment saved'),
130
+ });
131
+
132
+ mutate(`/ws/rest/v1/programenrollment?patient=${patientUuid}&v=${customRepresentation}`);
133
+ }
134
+ },
135
+ (err) => {
136
+ createErrorHandler();
137
+
138
+ showNotification({
139
+ title: t('programEnrollmentSaveError', 'Error saving program enrollment'),
140
+ kind: 'error',
141
+ critical: true,
142
+ description: err?.message,
143
+ });
144
+ },
145
+ );
100
146
  return () => {
101
147
  sub.unsubscribe();
102
148
  };
103
149
  },
104
- [closeWorkspace, completionDate, enrollmentDate, mutate, patientUuid, selectedProgram, t, userLocation],
150
+ [
151
+ closeWorkspace,
152
+ completionDate,
153
+ enrollmentDate,
154
+ mutate,
155
+ patientUuid,
156
+ selectedProgram,
157
+ t,
158
+ userLocation,
159
+ currentEnrollment,
160
+ ],
105
161
  );
106
162
 
107
163
  return (
108
164
  <Form className={styles.form} onSubmit={handleSubmit}>
109
- <div className={styles.formContainer}>
110
- <FormGroup style={{ width: '50%' }} legendText={t('program', 'Program')}>
165
+ <Stack className={styles.formContainer} gap={7}>
166
+ <FormGroup style={{ maxWidth: '50%' }} legendText={t('programName', 'Program name')}>
111
167
  <div className={styles.selectContainer}>
112
168
  <Select
113
169
  id="program"
@@ -126,7 +182,7 @@ const ProgramsForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace, patient
126
182
  </Select>
127
183
  </div>
128
184
  </FormGroup>
129
- <FormGroup legendText={t('dateEnrolled', 'Date enrolled')}>
185
+ <FormGroup style={{ maxWidth: '50%' }} legendText={t('dateEnrolled', 'Date enrolled')}>
130
186
  <DatePicker
131
187
  id="enrollmentDate"
132
188
  datePickerType="single"
@@ -140,7 +196,7 @@ const ProgramsForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace, patient
140
196
  <DatePickerInput id="enrollmentDateInput" labelText="" />
141
197
  </DatePicker>
142
198
  </FormGroup>
143
- <FormGroup legendText={t('dateCompleted', 'Date completed')}>
199
+ <FormGroup style={{ width: '50%' }} legendText={t('dateCompleted', 'Date completed')}>
144
200
  <DatePicker
145
201
  id="completionDate"
146
202
  datePickerType="single"
@@ -173,7 +229,7 @@ const ProgramsForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace, patient
173
229
  ))}
174
230
  </Select>
175
231
  </FormGroup>
176
- </div>
232
+ </Stack>
177
233
  <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
178
234
  <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
179
235
  {t('cancel', 'Cancel')}
@@ -1,11 +1,4 @@
1
- @import "~@openmrs/esm-styleguide/src/vars";
2
- @import "~carbon-components/src/globals/scss/vars";
3
- @import "~carbon-components/src/globals/scss/mixins";
4
-
5
- .selectContainer {
6
- display: flex;
7
- align-items: center;
8
- }
1
+ @import '~@openmrs/esm-styleguide/src/vars';
9
2
 
10
3
  .loading {
11
4
  margin-left: 1rem;
@@ -13,7 +6,6 @@
13
6
  min-height: 2.5rem;
14
7
  }
15
8
 
16
-
17
9
  .button {
18
10
  height: 4rem;
19
11
  display: flex;