@openmrs/esm-patient-lists-app 11.3.1-patch.9310 → 11.3.1-patch.9508

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.
@@ -1,36 +1,29 @@
1
1
  import React from 'react';
2
2
  import { screen, render } from '@testing-library/react';
3
3
  import { openmrsFetch } from '@openmrs/esm-framework';
4
- import PatientListDetailsWorkspace, { type PatientListDetailsWorkspaceProps } from './patient-list-details.workspace';
5
- import { type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib';
4
+ import PatientListDetailsWorkspace from './patient-list-details.workspace';
6
5
 
7
6
  const mockOpenmrsFetch = openmrsFetch as jest.Mock;
8
7
 
9
- const defaultProps: PatientWorkspace2DefinitionProps<PatientListDetailsWorkspaceProps, {}> = {
10
- groupProps: {
11
- patientUuid: '',
12
- patient: undefined,
13
- visitContext: null,
14
- mutateVisitContext: null,
15
- },
8
+ const defaultProps = {
9
+ patientUuid: '',
10
+ patient: undefined,
11
+ promptBeforeClosing: jest.fn(),
16
12
  closeWorkspace: jest.fn(),
17
- workspaceProps: {
18
- list: {
19
- attributes: [],
20
- description:
21
- 'Cardiovascular Outcomes in Type 2 Diabetes (COTD Study): A Longitudinal Assessment of a New Diabetes Medication',
22
- id: '4dcb7061-fcad-4542-b219-4c197c117050',
23
- name: 'COTD Study',
24
- size: 2,
25
- startDate: '2023-11-14T23:45:51.000+0000',
26
- type: 'My List',
27
- },
13
+ closeWorkspaceWithSavedChanges: jest.fn(),
14
+ list: {
15
+ attributes: [],
16
+ description:
17
+ 'Cardiovascular Outcomes in Type 2 Diabetes (COTD Study): A Longitudinal Assessment of a New Diabetes Medication',
18
+ id: '4dcb7061-fcad-4542-b219-4c197c117050',
19
+ name: 'COTD Study',
20
+ size: 2,
21
+ startDate: '2023-11-14T23:45:51.000+0000',
22
+ type: 'My List',
28
23
  },
29
- workspaceName: '',
30
- launchChildWorkspace: jest.fn(),
31
- windowProps: {},
32
- windowName: '',
33
- isRootWorkspace: false,
24
+ setTitle: jest.fn(),
25
+ visitContext: null,
26
+ mutateVisitContext: null,
34
27
  };
35
28
 
36
29
  const mockPatientListData = [
@@ -123,7 +116,7 @@ it('renders the patient list details workspace', async () => {
123
116
 
124
117
  render(<PatientListDetailsWorkspace {...defaultProps} />);
125
118
 
126
- await screen.findByRole('heading', { name: defaultProps.workspaceProps.list.description });
119
+ await screen.findByRole('heading', { name: defaultProps.list.description });
127
120
 
128
121
  expect(screen.getByRole('button', { name: /back to patient lists/i })).toBeInTheDocument();
129
122
  expect(screen.getByText(/2 patients/i)).toBeInTheDocument();
@@ -1,56 +1,50 @@
1
1
  import React, { type ComponentProps, useCallback } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Button } from '@carbon/react';
4
- import { ArrowLeftIcon, formatDate, parseDate, Workspace2 } from '@openmrs/esm-framework';
5
- import { type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib';
4
+ import { ArrowLeftIcon, formatDate, launchWorkspace, parseDate } from '@openmrs/esm-framework';
5
+ import { type DefaultPatientWorkspaceProps } from '@openmrs/esm-patient-common-lib';
6
6
  import { type MappedList, usePatientListMembers } from '../patient-lists.resource';
7
7
  import PatientListDetailsTable from './patient-list-details-table.component';
8
8
  import styles from './patient-list-details.scss';
9
9
 
10
- export interface PatientListDetailsWorkspaceProps {
10
+ interface PatientListDetailsWorkspaceProps extends DefaultPatientWorkspaceProps {
11
11
  list: MappedList;
12
12
  }
13
13
 
14
- const PatientListDetailsWorkspace: React.FC<PatientWorkspace2DefinitionProps<PatientListDetailsWorkspaceProps, {}>> = ({
15
- workspaceProps: { list },
16
- closeWorkspace,
17
- }) => {
14
+ function PatientListDetailsWorkspace({ list }: PatientListDetailsWorkspaceProps) {
18
15
  const { t } = useTranslation();
19
16
  const { listMembers, isLoading } = usePatientListMembers(list.id);
20
17
 
21
- const closeListDetailsWorkspace = useCallback(() => closeWorkspace(), [closeWorkspace]);
18
+ const closeListDetailsWorkspace = useCallback(() => {
19
+ launchWorkspace('patient-lists');
20
+ }, []);
22
21
 
23
22
  return (
24
- <Workspace2
25
- title={list.name || t('patientListDetailWorkspaceTitle', 'Patient List Details')}
26
- hasUnsavedChanges={false}
27
- >
28
- <main className={styles.container}>
29
- <section className={styles.header}>
30
- <h4 className={styles.description}>{list.description ?? '--'}</h4>
31
- <p className={styles.details}>
32
- {list.size} {t('patients', 'patients')} &middot;{' '}
33
- <span className={styles.label}>{t('createdOn', 'Created on')}:</span>{' '}
34
- {list.startDate ? formatDate(parseDate(list.startDate)) : null}
35
- </p>
36
- </section>
37
- <div className={styles.backButton}>
38
- <Button
39
- kind="ghost"
40
- renderIcon={(props: ComponentProps<typeof ArrowLeftIcon>) => <ArrowLeftIcon size={24} {...props} />}
41
- iconDescription="Back to patient lists"
42
- size="sm"
43
- onClick={closeListDetailsWorkspace}
44
- >
45
- <span>{t('backToPatientLists', 'Back to patient lists')}</span>
46
- </Button>
47
- </div>
48
- <section className={styles.tableContainer}>
49
- <PatientListDetailsTable isLoading={isLoading} listMembers={listMembers} />
50
- </section>
51
- </main>
52
- </Workspace2>
23
+ <main className={styles.container}>
24
+ <section className={styles.header}>
25
+ <h4 className={styles.description}>{list.description ?? '--'}</h4>
26
+ <p className={styles.details}>
27
+ {list.size} {t('patients', 'patients')} &middot;{' '}
28
+ <span className={styles.label}>{t('createdOn', 'Created on')}:</span>{' '}
29
+ {list.startDate ? formatDate(parseDate(list.startDate)) : null}
30
+ </p>
31
+ </section>
32
+ <div className={styles.backButton}>
33
+ <Button
34
+ kind="ghost"
35
+ renderIcon={(props: ComponentProps<typeof ArrowLeftIcon>) => <ArrowLeftIcon size={24} {...props} />}
36
+ iconDescription="Back to patient lists"
37
+ size="sm"
38
+ onClick={closeListDetailsWorkspace}
39
+ >
40
+ <span>{t('backToPatientLists', 'Back to patient lists')}</span>
41
+ </Button>
42
+ </div>
43
+ <section className={styles.tableContainer}>
44
+ <PatientListDetailsTable isLoading={isLoading} listMembers={listMembers} />
45
+ </section>
46
+ </main>
53
47
  );
54
- };
48
+ }
55
49
 
56
50
  export default PatientListDetailsWorkspace;
@@ -23,7 +23,8 @@ it('renders an empty state if patient list data is unavailable', async () => {
23
23
  mutate: jest.fn(),
24
24
  isValidating: false,
25
25
  });
26
- renderPatientListWorkspace();
26
+
27
+ render(<PatientListsWorkspace />);
27
28
 
28
29
  expect(screen.getByTitle(/empty data illustration/i)).toBeInTheDocument();
29
30
  expect(screen.getByText(/no patient lists to display/i)).toBeInTheDocument();
@@ -51,7 +52,7 @@ it('renders a tabular overview of the available patient lists', async () => {
51
52
  isValidating: false,
52
53
  });
53
54
 
54
- renderPatientListWorkspace();
55
+ render(<PatientListsWorkspace />);
55
56
 
56
57
  await screen.findByRole('table');
57
58
 
@@ -74,23 +75,3 @@ it('renders a tabular overview of the available patient lists', async () => {
74
75
  await user.type(searchbox, 'COTD');
75
76
  expect(screen.getByRole('row', { name: /COTD Study My List 2/i })).toBeInTheDocument();
76
77
  });
77
-
78
- function renderPatientListWorkspace() {
79
- render(
80
- <PatientListsWorkspace
81
- workspaceName={''}
82
- launchChildWorkspace={jest.fn()}
83
- closeWorkspace={jest.fn()}
84
- workspaceProps={{}}
85
- windowProps={{}}
86
- groupProps={{
87
- mutateVisitContext: jest.fn(),
88
- patient: null,
89
- patientUuid: null,
90
- visitContext: null,
91
- }}
92
- windowName={''}
93
- isRootWorkspace={false}
94
- />,
95
- );
96
- }
@@ -18,24 +18,21 @@ import {
18
18
  TableToolbarSearch,
19
19
  Tile,
20
20
  } from '@carbon/react';
21
- import { EmptyDataIllustration, type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib';
22
- import { launchWorkspace, useLayoutType, Workspace2 } from '@openmrs/esm-framework';
23
- import { usePatientLists } from '../patient-lists.resource';
21
+ import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
22
+ import { launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
23
+ import { usePatientLists, type MappedList } from '../patient-lists.resource';
24
24
  import styles from './patient-lists.scss';
25
25
 
26
- function PatientListsWorkspace({ launchChildWorkspace }: PatientWorkspace2DefinitionProps<{}, {}>) {
26
+ function PatientListsWorkspace() {
27
27
  const { t } = useTranslation();
28
28
  const layout = useLayoutType();
29
29
  const responsiveSize = layout === 'tablet' ? 'lg' : 'sm';
30
30
  const [searchTerm, setSearchTerm] = useState('');
31
31
  const { patientLists, isLoading } = usePatientLists();
32
32
 
33
- const launchListDetailsWorkspace = useCallback(
34
- (list) => {
35
- launchChildWorkspace('patient-list-details', { list });
36
- },
37
- [launchChildWorkspace],
38
- );
33
+ const launchListDetailsWorkspace = useCallback((list: MappedList) => {
34
+ launchWorkspace('patient-list-details', { list, workspaceTitle: list.name });
35
+ }, []);
39
36
 
40
37
  const tableHeaders = [
41
38
  {
@@ -69,96 +66,90 @@ function PatientListsWorkspace({ launchChildWorkspace }: PatientWorkspace2Defini
69
66
 
70
67
  if (isLoading)
71
68
  return (
72
- <Workspace2 title={t('patientListsWorkspaceTitle', 'Patient Lists')} hasUnsavedChanges={false}>
73
- <div className={styles.skeletonContainer}>
74
- <DataTableSkeleton className={styles.dataTableSkeleton} rowCount={5} columnCount={5} zebra />
75
- </div>
76
- </Workspace2>
69
+ <div className={styles.skeletonContainer}>
70
+ <DataTableSkeleton className={styles.dataTableSkeleton} rowCount={5} columnCount={5} zebra />
71
+ </div>
77
72
  );
78
73
 
79
74
  if (patientLists?.length > 0) {
80
75
  return (
81
- <Workspace2 title={t('patientListsWorkspaceTitle', 'Patient Lists')} hasUnsavedChanges={false}>
82
- <section className={styles.container}>
83
- <DataTable headers={tableHeaders} rows={filteredLists || []} size={responsiveSize} useZebraStyles>
84
- {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
85
- <>
86
- <TableContainer className={styles.tableContainer}>
87
- <div className={styles.toolbarWrapper}>
88
- <TableToolbar className={styles.tableToolbar}>
89
- <TableToolbarContent>
90
- <TableToolbarSearch
91
- className={styles.search}
92
- expanded
93
- onChange={handleSearchTermChange}
94
- placeholder={t('searchThisList', 'Search this list')}
95
- size={responsiveSize}
96
- />
97
- </TableToolbarContent>
98
- </TableToolbar>
99
- </div>
100
- {rows.length > 0 ? (
101
- <Table aria-label="patient lists" className={styles.table} {...getTableProps()}>
102
- <TableHead>
103
- <TableRow>
104
- {headers.map((header) => (
105
- <TableHeader key={header.key} {...getHeaderProps({ header })}>
106
- {header.header}
107
- </TableHeader>
108
- ))}
109
- </TableRow>
110
- </TableHead>
111
- <TableBody>
112
- {rows.map((row) => {
113
- const currentList = patientLists?.find((list) => list?.id === row.id);
76
+ <section className={styles.container}>
77
+ <DataTable headers={tableHeaders} rows={filteredLists || []} size={responsiveSize} useZebraStyles>
78
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
79
+ <>
80
+ <TableContainer className={styles.tableContainer}>
81
+ <div className={styles.toolbarWrapper}>
82
+ <TableToolbar className={styles.tableToolbar}>
83
+ <TableToolbarContent>
84
+ <TableToolbarSearch
85
+ className={styles.search}
86
+ expanded
87
+ onChange={handleSearchTermChange}
88
+ placeholder={t('searchThisList', 'Search this list')}
89
+ size="sm"
90
+ />
91
+ </TableToolbarContent>
92
+ </TableToolbar>
93
+ </div>
94
+ {rows.length > 0 ? (
95
+ <Table aria-label="patient lists" className={styles.table} {...getTableProps()}>
96
+ <TableHead>
97
+ <TableRow>
98
+ {headers.map((header) => (
99
+ <TableHeader key={header.key} {...getHeaderProps({ header })}>
100
+ {header.header}
101
+ </TableHeader>
102
+ ))}
103
+ </TableRow>
104
+ </TableHead>
105
+ <TableBody>
106
+ {rows.map((row) => {
107
+ const currentList = patientLists?.find((list) => list?.id === row.id);
114
108
 
115
- return (
116
- <TableRow {...getRowProps({ row })} key={row.id}>
117
- <TableCell>
118
- <Link className={styles.link} onClick={() => launchListDetailsWorkspace(currentList)}>
119
- {currentList?.name ?? ''}
120
- </Link>
121
- </TableCell>
122
- <TableCell>{currentList?.type ?? ''}</TableCell>
123
- <TableCell>{currentList?.size ?? ''}</TableCell>
124
- </TableRow>
125
- );
126
- })}
127
- </TableBody>
128
- </Table>
129
- ) : null}
130
- </TableContainer>
131
- {filteredLists?.length === 0 ? (
132
- <div className={styles.tileContainer}>
133
- <Tile className={styles.tile}>
134
- <div className={styles.tileContent}>
135
- <p className={styles.content}>{t('noMatchingListsFound', 'No matching lists to display')}</p>
136
- <p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
137
- </div>
138
- </Tile>
139
- </div>
109
+ return (
110
+ <TableRow {...getRowProps({ row })} key={row.id}>
111
+ <TableCell>
112
+ <Link className={styles.link} onClick={() => launchListDetailsWorkspace(currentList)}>
113
+ {currentList?.name ?? ''}
114
+ </Link>
115
+ </TableCell>
116
+ <TableCell>{currentList?.type ?? ''}</TableCell>
117
+ <TableCell>{currentList?.size ?? ''}</TableCell>
118
+ </TableRow>
119
+ );
120
+ })}
121
+ </TableBody>
122
+ </Table>
140
123
  ) : null}
141
- </>
142
- )}
143
- </DataTable>
144
- </section>
145
- </Workspace2>
124
+ </TableContainer>
125
+ {filteredLists?.length === 0 ? (
126
+ <div className={styles.tileContainer}>
127
+ <Tile className={styles.tile}>
128
+ <div className={styles.tileContent}>
129
+ <p className={styles.content}>{t('noMatchingListsFound', 'No matching lists to display')}</p>
130
+ <p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
131
+ </div>
132
+ </Tile>
133
+ </div>
134
+ ) : null}
135
+ </>
136
+ )}
137
+ </DataTable>
138
+ </section>
146
139
  );
147
140
  }
148
141
 
149
142
  return (
150
- <Workspace2 title={t('patientListsWorkspaceTitle', 'Patient Lists')} hasUnsavedChanges={false}>
151
- <div className={styles.emptyStateContainer}>
152
- <Layer>
153
- <Tile className={styles.emptyStateTile}>
154
- <div className={styles.tileContent}>
155
- <EmptyDataIllustration />
156
- <p className={styles.emptyStateContent}>{t('noPatientListsToDisplay', 'No patient lists to display')}</p>
157
- </div>
158
- </Tile>
159
- </Layer>
160
- </div>
161
- </Workspace2>
143
+ <div className={styles.emptyStateContainer}>
144
+ <Layer>
145
+ <Tile className={styles.emptyStateTile}>
146
+ <div className={styles.tileContent}>
147
+ <EmptyDataIllustration />
148
+ <p className={styles.emptyStateContent}>{t('noPatientListsToDisplay', 'No patient lists to display')}</p>
149
+ </div>
150
+ </Tile>
151
+ </Layer>
152
+ </div>
162
153
  );
163
154
  }
164
155
 
@@ -0,0 +1,21 @@
1
+ {
2
+ "backToPatientLists": "Back to patient lists",
3
+ "checkFilters": "Check the filters above",
4
+ "createdOn": "Created on",
5
+ "identifier": "Identifier",
6
+ "listName": "List name",
7
+ "listType": "List type",
8
+ "name": "Name",
9
+ "noMatchingListsFound": "No matching lists to display",
10
+ "noMatchingPatients": "No matching patients to display",
11
+ "noPatientListsToDisplay": "No patient lists to display",
12
+ "noPatientsInList": "There are no patients in this list",
13
+ "numberOfPatients": "No. of patients",
14
+ "patientListDetailWorkspaceTitle": "Patient List Details",
15
+ "patientLists": "Patient lists",
16
+ "patientListsWorkspaceTitle": "Patient Lists",
17
+ "patients": "patients",
18
+ "searchThisList": "Search this list",
19
+ "sex": "Sex",
20
+ "startDate": "Start Date"
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "backToPatientLists": "Back to patient lists",
3
+ "checkFilters": "Check the filters above",
4
+ "createdOn": "Created on",
5
+ "identifier": "Identifier",
6
+ "listName": "List name",
7
+ "listType": "List type",
8
+ "name": "Name",
9
+ "noMatchingListsFound": "No matching lists to display",
10
+ "noMatchingPatients": "No matching patients to display",
11
+ "noPatientListsToDisplay": "No patient lists to display",
12
+ "noPatientsInList": "There are no patients in this list",
13
+ "numberOfPatients": "No. of patients",
14
+ "patientListDetailWorkspaceTitle": "Patient List Details",
15
+ "patientLists": "Patient lists",
16
+ "patientListsWorkspaceTitle": "Patient Lists",
17
+ "patients": "patients",
18
+ "searchThisList": "Search this list",
19
+ "sex": "Sex",
20
+ "startDate": "Start Date"
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "backToPatientLists": "Back to patient lists",
3
+ "checkFilters": "Check the filters above",
4
+ "createdOn": "Created on",
5
+ "identifier": "Identifier",
6
+ "listName": "List name",
7
+ "listType": "List type",
8
+ "name": "Name",
9
+ "noMatchingListsFound": "No matching lists to display",
10
+ "noMatchingPatients": "No matching patients to display",
11
+ "noPatientListsToDisplay": "No patient lists to display",
12
+ "noPatientsInList": "There are no patients in this list",
13
+ "numberOfPatients": "No. of patients",
14
+ "patientListDetailWorkspaceTitle": "Patient List Details",
15
+ "patientLists": "Patient lists",
16
+ "patientListsWorkspaceTitle": "Patient Lists",
17
+ "patients": "patients",
18
+ "searchThisList": "Search this list",
19
+ "sex": "Sex",
20
+ "startDate": "Start Date"
21
+ }