@openmrs/esm-patient-list-management-app 9.2.1-pre.7172 → 9.2.1-pre.7177
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/.turbo/turbo-build.log +5 -5
- package/dist/1992.js +1 -1
- package/dist/1992.js.map +1 -1
- package/dist/4300.js +1 -1
- package/dist/7760.js +1 -1
- package/dist/7760.js.map +1 -1
- package/dist/8984.js +1 -1
- package/dist/8984.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-list-management-app.js.buildmanifest.json +17 -17
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/api/hooks.ts +2 -1
- package/src/api/types.ts +1 -0
- package/src/config-schema.ts +6 -0
- package/src/lists-table/lists-table.component.tsx +66 -17
- package/src/lists-table/lists-table.scss +5 -0
- package/src/lists-table/lists-table.test.tsx +76 -0
- package/translations/en.json +2 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Button,
|
|
8
8
|
DataTable,
|
|
9
9
|
DataTableSkeleton,
|
|
10
|
+
Dropdown,
|
|
10
11
|
InlineLoading,
|
|
11
12
|
Layer,
|
|
12
13
|
Search,
|
|
@@ -71,13 +72,50 @@ const ListsTable: React.FC<PatientListTableProps> = ({
|
|
|
71
72
|
const id = useId();
|
|
72
73
|
const layout = useLayoutType();
|
|
73
74
|
const config = useConfig<PatientListManagementConfig>();
|
|
75
|
+
const { sessionLocation } = useSession();
|
|
74
76
|
const pageSize = config.patientListsToShow ?? 10;
|
|
75
77
|
const [sortParams, setSortParams] = useState({ key: '', order: 'none' });
|
|
76
78
|
const [searchTerm, setSearchTerm] = useState('');
|
|
77
|
-
const responsiveSize = layout === 'tablet' ? 'lg' : '
|
|
79
|
+
const responsiveSize = layout === 'tablet' ? 'lg' : 'md';
|
|
78
80
|
|
|
79
81
|
const { toggleStarredList, starredLists } = useStarredLists();
|
|
80
82
|
|
|
83
|
+
const allLocationsItem = useMemo(() => ({ id: 'all', label: t('allLocations', 'All Locations') }), [t]);
|
|
84
|
+
|
|
85
|
+
const [selectedLocation, setSelectedLocation] = useState<string>(() => {
|
|
86
|
+
if (config.defaultToCurrentLocation && sessionLocation?.uuid) {
|
|
87
|
+
return sessionLocation.uuid;
|
|
88
|
+
}
|
|
89
|
+
return 'all';
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const locationOptions = useMemo(() => {
|
|
93
|
+
const locationsMap = new Map<string, string>();
|
|
94
|
+
patientLists.forEach((list) => {
|
|
95
|
+
if (list.location) {
|
|
96
|
+
locationsMap.set(list.location.uuid, list.location.display);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (sessionLocation?.uuid) {
|
|
100
|
+
locationsMap.set(sessionLocation.uuid, sessionLocation.display);
|
|
101
|
+
}
|
|
102
|
+
const sortedLocations = Array.from(locationsMap.entries())
|
|
103
|
+
.map(([uuid, display]) => ({ id: uuid, label: display }))
|
|
104
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
105
|
+
return [allLocationsItem, ...sortedLocations];
|
|
106
|
+
}, [patientLists, sessionLocation, allLocationsItem]);
|
|
107
|
+
|
|
108
|
+
const initialDropdownItem = useMemo(() => {
|
|
109
|
+
return locationOptions.find((loc) => loc.id === selectedLocation) || allLocationsItem;
|
|
110
|
+
}, [selectedLocation, locationOptions, allLocationsItem]);
|
|
111
|
+
|
|
112
|
+
const locationFilteredLists = useMemo(() => {
|
|
113
|
+
if (selectedLocation === 'all') {
|
|
114
|
+
return patientLists;
|
|
115
|
+
}
|
|
116
|
+
return patientLists.filter((list) => list.location?.uuid === selectedLocation);
|
|
117
|
+
}, [patientLists, selectedLocation]);
|
|
118
|
+
|
|
81
119
|
function customSortRow(listA, listB, { sortDirection, sortStates, ...props }) {
|
|
82
120
|
const { key } = props;
|
|
83
121
|
setSortParams({ key, order: sortDirection });
|
|
@@ -85,14 +123,14 @@ const ListsTable: React.FC<PatientListTableProps> = ({
|
|
|
85
123
|
|
|
86
124
|
const filteredLists: Array<PatientList> = useMemo(() => {
|
|
87
125
|
if (!searchTerm) {
|
|
88
|
-
return
|
|
126
|
+
return locationFilteredLists;
|
|
89
127
|
}
|
|
90
128
|
|
|
91
129
|
return fuzzy
|
|
92
|
-
.filter(searchTerm,
|
|
130
|
+
.filter(searchTerm, locationFilteredLists, { extract: (list) => `${list.display} ${list.type}` })
|
|
93
131
|
.sort((r1, r2) => r1.score - r2.score)
|
|
94
132
|
.map((result) => result.original);
|
|
95
|
-
}, [
|
|
133
|
+
}, [locationFilteredLists, searchTerm]);
|
|
96
134
|
|
|
97
135
|
const { key, order } = sortParams;
|
|
98
136
|
const sortedData =
|
|
@@ -139,23 +177,34 @@ const ListsTable: React.FC<PatientListTableProps> = ({
|
|
|
139
177
|
<>
|
|
140
178
|
<div id="tableToolBar" className={styles.searchContainer}>
|
|
141
179
|
<div>{isValidating && <InlineLoading />}</div>
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
180
|
+
<div className={styles.filterContainer}>
|
|
181
|
+
<Layer>
|
|
182
|
+
<Dropdown
|
|
183
|
+
id="location-filter-dropdown"
|
|
184
|
+
initialSelectedItem={initialDropdownItem}
|
|
185
|
+
items={locationOptions}
|
|
186
|
+
label=""
|
|
187
|
+
onChange={({ selectedItem }) => setSelectedLocation(selectedItem.id)}
|
|
188
|
+
titleText={t('filterByLocation', 'Filter by location')}
|
|
189
|
+
type="inline"
|
|
190
|
+
/>
|
|
191
|
+
</Layer>
|
|
192
|
+
<Layer>
|
|
193
|
+
<Search
|
|
194
|
+
className={styles.searchbox}
|
|
195
|
+
id={`${id}-search`}
|
|
196
|
+
labelText={t('searchThisList', 'Search this list')}
|
|
197
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
|
|
198
|
+
placeholder={t('searchThisList', 'Search this list')}
|
|
199
|
+
size={responsiveSize}
|
|
200
|
+
value={searchTerm}
|
|
201
|
+
/>
|
|
202
|
+
</Layer>
|
|
203
|
+
</div>
|
|
153
204
|
</div>
|
|
154
205
|
<DataTable rows={tableRows} headers={headers} size={responsiveSize} sortRow={customSortRow}>
|
|
155
206
|
{({ rows, headers, getTableProps, getHeaderProps, getRowProps, getTableContainerProps }) => (
|
|
156
207
|
<TableContainer {...getTableContainerProps()} className={styles.tableContainer}>
|
|
157
|
-
{/* data-tutorial-target attribute is essential for joyride in onboarding app ! */}
|
|
158
|
-
|
|
159
208
|
<Table
|
|
160
209
|
{...getTableProps()}
|
|
161
210
|
className={styles.table}
|
|
@@ -25,6 +25,7 @@ const patientLists: Array<PatientList> = [
|
|
|
25
25
|
"Children's Obstructive Lung Disease, Bronchiectasis and Antibiotic Tolerance Study (investigates novel antibiotic treatment for persistent childhood lung infections)",
|
|
26
26
|
type: 'My List',
|
|
27
27
|
size: 200,
|
|
28
|
+
location: mockSession.data.sessionLocation,
|
|
28
29
|
},
|
|
29
30
|
{
|
|
30
31
|
id: 'f1b2ca00-6742-490d-9062-5025644c7632',
|
|
@@ -33,6 +34,7 @@ const patientLists: Array<PatientList> = [
|
|
|
33
34
|
'Genomic Evaluation of Neonatal Early Sepsis in Infants - Stratified for Risk Factors (examines genetic factors influencing sepsis risk in newborns).',
|
|
34
35
|
type: 'My List',
|
|
35
36
|
size: 300,
|
|
37
|
+
location: { uuid: 'loc-2', display: 'Location 2' },
|
|
36
38
|
},
|
|
37
39
|
{
|
|
38
40
|
id: '9c5e8677-6747-4315-84c7-a20f30d795e2',
|
|
@@ -41,6 +43,7 @@ const patientLists: Array<PatientList> = [
|
|
|
41
43
|
'Vascular Imaging and Genomics of Onset and Recovery in Stroke (analyzes cardiovascular and genetic markers predictive of stroke outcomes)',
|
|
42
44
|
type: 'My List',
|
|
43
45
|
size: 500,
|
|
46
|
+
location: mockSession.data.sessionLocation,
|
|
44
47
|
},
|
|
45
48
|
{
|
|
46
49
|
id: 'be10d553-b183-4647-9be6-160d1246de8a',
|
|
@@ -49,6 +52,7 @@ const patientLists: Array<PatientList> = [
|
|
|
49
52
|
'Equitable Quality in Cancer Treatment for Underserved Young Adults (assesses healthcare disparities in cancer treatment for young adults from disadvantaged backgrounds)',
|
|
50
53
|
type: 'My List',
|
|
51
54
|
size: 100,
|
|
55
|
+
location: null,
|
|
52
56
|
},
|
|
53
57
|
{
|
|
54
58
|
id: '72a84c22-2425-4501-95b7-820b793602f3',
|
|
@@ -57,6 +61,7 @@ const patientLists: Array<PatientList> = [
|
|
|
57
61
|
'Mental Illness and Neuroimaging Study for Personalized Assessment and Care Evaluation (develops personalized treatment plans for mental illness based on brain imaging and individual factors)',
|
|
58
62
|
type: 'My List',
|
|
59
63
|
size: 250,
|
|
64
|
+
location: { uuid: 'loc-2', display: 'Location 2' },
|
|
60
65
|
},
|
|
61
66
|
{
|
|
62
67
|
id: '06ca3df6-92d6-4401-8f06-4f094b004425',
|
|
@@ -65,6 +70,7 @@ const patientLists: Array<PatientList> = [
|
|
|
65
70
|
'Mediterranean Diet, Exercise, and Nutrition for Diabetes (evaluates the combined effects of dietary and lifestyle changes on diabetes management).',
|
|
66
71
|
type: 'My List',
|
|
67
72
|
size: 150,
|
|
73
|
+
location: { uuid: 'loc-3', display: 'Location 3' },
|
|
68
74
|
},
|
|
69
75
|
];
|
|
70
76
|
|
|
@@ -186,4 +192,74 @@ describe('ListsTable', () => {
|
|
|
186
192
|
await user.click(starListButton);
|
|
187
193
|
await screen.findByRole('button', { name: /^unstar list$/i });
|
|
188
194
|
});
|
|
195
|
+
|
|
196
|
+
it('filters patient lists by location', async () => {
|
|
197
|
+
const user = userEvent.setup();
|
|
198
|
+
render(<ListsTable patientLists={patientLists} listType={''} headers={tableHeaders} isLoading={false} />);
|
|
199
|
+
|
|
200
|
+
const locationFilter = screen.getByRole('combobox', { name: /filter by location/i });
|
|
201
|
+
|
|
202
|
+
// Initial state: All locations
|
|
203
|
+
expect(locationFilter).toHaveTextContent('All Locations');
|
|
204
|
+
expect(screen.getByText('COBALT Cohort')).toBeInTheDocument();
|
|
205
|
+
expect(screen.getByText('GENESIS Cohort')).toBeInTheDocument();
|
|
206
|
+
expect(screen.getByText('VIGOR Cohort')).toBeInTheDocument();
|
|
207
|
+
expect(screen.getByText('EQUITY Cohort')).toBeInTheDocument();
|
|
208
|
+
expect(screen.getByText('MINDSCAPE Cohort')).toBeInTheDocument();
|
|
209
|
+
expect(screen.getByText('MEND Cohort')).toBeInTheDocument();
|
|
210
|
+
|
|
211
|
+
await user.click(locationFilter);
|
|
212
|
+
await user.click(screen.getByRole('option', { name: mockSession.data.sessionLocation.display }));
|
|
213
|
+
|
|
214
|
+
expect(locationFilter).toHaveTextContent('Inpatient Ward');
|
|
215
|
+
expect(screen.getByText('COBALT Cohort')).toBeInTheDocument();
|
|
216
|
+
expect(screen.getByText('VIGOR Cohort')).toBeInTheDocument();
|
|
217
|
+
expect(screen.queryByText('GENESIS Cohort')).not.toBeInTheDocument();
|
|
218
|
+
expect(screen.queryByText('EQUITY Cohort')).not.toBeInTheDocument();
|
|
219
|
+
expect(screen.queryByText('MINDSCAPE Cohort')).not.toBeInTheDocument();
|
|
220
|
+
expect(screen.queryByText('MEND Cohort')).not.toBeInTheDocument();
|
|
221
|
+
|
|
222
|
+
await user.click(locationFilter);
|
|
223
|
+
await user.click(screen.getByRole('option', { name: 'Location 2' }));
|
|
224
|
+
|
|
225
|
+
expect(locationFilter).toHaveTextContent('Location 2');
|
|
226
|
+
expect(screen.getByText('GENESIS Cohort')).toBeInTheDocument();
|
|
227
|
+
expect(screen.getByText('MINDSCAPE Cohort')).toBeInTheDocument();
|
|
228
|
+
expect(screen.queryByText('COBALT Cohort')).not.toBeInTheDocument();
|
|
229
|
+
expect(screen.queryByText('VIGOR Cohort')).not.toBeInTheDocument();
|
|
230
|
+
expect(screen.queryByText('EQUITY Cohort')).not.toBeInTheDocument();
|
|
231
|
+
expect(screen.queryByText('MEND Cohort')).not.toBeInTheDocument();
|
|
232
|
+
|
|
233
|
+
// Filter back to All Locations
|
|
234
|
+
await user.click(locationFilter);
|
|
235
|
+
await user.click(screen.getByRole('option', { name: 'All Locations' }));
|
|
236
|
+
|
|
237
|
+
expect(locationFilter).toHaveTextContent('All Locations');
|
|
238
|
+
expect(screen.getByText('COBALT Cohort')).toBeInTheDocument();
|
|
239
|
+
expect(screen.getByText('GENESIS Cohort')).toBeInTheDocument();
|
|
240
|
+
expect(screen.getByText('VIGOR Cohort')).toBeInTheDocument();
|
|
241
|
+
expect(screen.getByText('EQUITY Cohort')).toBeInTheDocument();
|
|
242
|
+
expect(screen.getByText('MINDSCAPE Cohort')).toBeInTheDocument();
|
|
243
|
+
expect(screen.getByText('MEND Cohort')).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('defaults to the current session location if configured', async () => {
|
|
247
|
+
mockUseConfig.mockReturnValue({
|
|
248
|
+
...getDefaultsFromConfigSchema(configSchema),
|
|
249
|
+
patientListsToShow: 10,
|
|
250
|
+
defaultToCurrentLocation: true,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
render(<ListsTable patientLists={patientLists} listType={''} headers={tableHeaders} isLoading={false} />);
|
|
254
|
+
|
|
255
|
+
const locationFilter = screen.getByRole('combobox', { name: /filter by location/i });
|
|
256
|
+
expect(locationFilter).toHaveTextContent('Inpatient Ward');
|
|
257
|
+
|
|
258
|
+
expect(screen.getByText('COBALT Cohort')).toBeInTheDocument();
|
|
259
|
+
expect(screen.getByText('VIGOR Cohort')).toBeInTheDocument();
|
|
260
|
+
expect(screen.queryByText('GENESIS Cohort')).not.toBeInTheDocument();
|
|
261
|
+
expect(screen.queryByText('EQUITY Cohort')).not.toBeInTheDocument();
|
|
262
|
+
expect(screen.queryByText('MINDSCAPE Cohort')).not.toBeInTheDocument();
|
|
263
|
+
expect(screen.queryByText('MEND Cohort')).not.toBeInTheDocument();
|
|
264
|
+
});
|
|
189
265
|
});
|
package/translations/en.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"add": "Add",
|
|
4
4
|
"addPatientToList": "Add patient to list",
|
|
5
5
|
"allLists": "All lists",
|
|
6
|
+
"allLocations": "All Locations",
|
|
6
7
|
"backToListsPage": "Back to lists page",
|
|
7
8
|
"cancel": "Cancel",
|
|
8
9
|
"cannotAddPatient": "Cannot add patient",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
"errorDeletingList": "Error deleting patient list",
|
|
36
37
|
"errorRemovingPatientFromList": "Failed to remove patient from list",
|
|
37
38
|
"errorUpdatingList": "Error updating list",
|
|
39
|
+
"filterByLocation": "Filter by location",
|
|
38
40
|
"identifier": "Identifier",
|
|
39
41
|
"items": "items",
|
|
40
42
|
"itemsDisplayed": "{{numberOfItemsDisplayed}} items",
|