@openmrs/esm-dispensing-app 1.9.2-pre.881 → 1.9.2-pre.885
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/cache/93c39a835dc53d99-meta.json +1 -0
- package/.turbo/cache/93c39a835dc53d99.tar.zst +0 -0
- package/.turbo/turbo-build.log +7 -7
- package/dist/1282.js +1 -0
- package/dist/1282.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-dispensing-app.js +1 -1
- package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +30 -30
- package/dist/routes.json +1 -1
- package/e2e/commands/encounter-operations.ts +1 -4
- package/e2e/specs/close-prescription.spec.ts +5 -0
- package/e2e/specs/dispense-medication.spec.ts +4 -0
- package/e2e/specs/pause-prescription.spec.ts +5 -0
- package/package.json +1 -1
- package/src/medication-request/medication-request.resource.test.tsx +3 -3
- package/src/medication-request/medication-request.resource.tsx +12 -9
- package/src/prescriptions/patient-search-tab-panel.component.tsx +58 -0
- package/src/prescriptions/patient-search-tab-panel.scss +26 -0
- package/src/prescriptions/prescription-tab-lists.component.tsx +18 -84
- package/src/prescriptions/prescription-tab-panel.component.tsx +45 -148
- package/src/prescriptions/prescriptions-table.component.tsx +164 -0
- package/translations/en.json +3 -0
- package/.turbo/cache/a8c56cb22f16ba44-meta.json +0 -1
- package/.turbo/cache/a8c56cb22f16ba44.tar.zst +0 -0
- package/dist/6411.js +0 -1
- package/dist/6411.js.map +0 -1
|
@@ -1,165 +1,62 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DataTable,
|
|
3
|
-
DataTableSkeleton,
|
|
4
|
-
Layer,
|
|
5
|
-
Pagination,
|
|
6
|
-
Table,
|
|
7
|
-
TableBody,
|
|
8
|
-
TableCell,
|
|
9
|
-
TableContainer,
|
|
10
|
-
TableExpandedRow,
|
|
11
|
-
TableExpandHeader,
|
|
12
|
-
TableExpandRow,
|
|
13
|
-
TableHead,
|
|
14
|
-
TableHeader,
|
|
15
|
-
TableRow,
|
|
16
|
-
TabPanel,
|
|
17
|
-
Tile,
|
|
18
|
-
} from '@carbon/react';
|
|
19
|
-
import { formatDatetime, parseDate, useConfig } from '@openmrs/esm-framework';
|
|
20
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
21
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { ComboBox, Search, TabPanel } from '@carbon/react';
|
|
4
|
+
import { useConfig, useDebounce } from '@openmrs/esm-framework';
|
|
22
5
|
import { type PharmacyConfig } from '../config-schema';
|
|
23
|
-
import
|
|
24
|
-
import PatientInfoCell from '../patient/patient-info-cell.component';
|
|
25
|
-
import PrescriptionExpanded from './prescription-expanded.component';
|
|
6
|
+
import PrescriptionsTable from './prescriptions-table.component';
|
|
26
7
|
import styles from './prescriptions.scss';
|
|
8
|
+
import { useLocationForFiltering } from '../location/location.resource';
|
|
9
|
+
import { type SimpleLocation } from '../types';
|
|
27
10
|
|
|
28
11
|
interface PrescriptionTabPanelProps {
|
|
29
|
-
searchTerm: string;
|
|
30
|
-
location: string;
|
|
31
12
|
status: string;
|
|
13
|
+
isTabActive: boolean;
|
|
32
14
|
}
|
|
33
15
|
|
|
34
|
-
const PrescriptionTabPanel: React.FC<PrescriptionTabPanelProps> = ({
|
|
16
|
+
const PrescriptionTabPanel: React.FC<PrescriptionTabPanelProps> = ({ status, isTabActive }) => {
|
|
35
17
|
const { t } = useTranslation();
|
|
36
18
|
const config = useConfig<PharmacyConfig>();
|
|
37
|
-
const
|
|
38
|
-
const [
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
pageSize,
|
|
42
|
-
nextOffSet,
|
|
43
|
-
searchTerm,
|
|
44
|
-
location,
|
|
45
|
-
status,
|
|
46
|
-
config.medicationRequestExpirationPeriodInDays,
|
|
47
|
-
config.refreshInterval,
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// dynamic status keys we need to maintain
|
|
51
|
-
// t('active', 'Active')
|
|
52
|
-
// t('paused', 'Paused')
|
|
53
|
-
// t('closed', 'Closed')
|
|
54
|
-
// t('completed', 'Completed')
|
|
55
|
-
// t('expired', 'Expired')
|
|
56
|
-
// t('cancelled', 'Cancelled')
|
|
57
|
-
|
|
58
|
-
let columns = [
|
|
59
|
-
{ header: t('created', 'Created'), key: 'created' },
|
|
60
|
-
{ header: t('patientName', 'Patient name'), key: 'patient' },
|
|
61
|
-
{ header: t('prescriber', 'Prescriber'), key: 'prescriber' },
|
|
62
|
-
{ header: t('drugs', 'Drugs'), key: 'drugs' },
|
|
63
|
-
{ header: t('lastDispenser', 'Last dispenser'), key: 'lastDispenser' },
|
|
64
|
-
{ header: t('status', 'Status'), key: 'status' },
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
// add the locations column, if enabled
|
|
68
|
-
if (config.locationBehavior?.locationColumn?.enabled) {
|
|
69
|
-
columns = [...columns.slice(0, 3), { header: t('location', 'Location'), key: 'location' }, ...columns.slice(3)];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// reset back to page 1 whenever search term changes
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
setPage(1);
|
|
75
|
-
setNextOffSet(0);
|
|
76
|
-
}, [searchTerm]);
|
|
19
|
+
const { filterLocations, isLoading: isFilterLocationsLoading } = useLocationForFiltering(config);
|
|
20
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
21
|
+
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
|
22
|
+
const [location, setLocation] = useState('');
|
|
77
23
|
|
|
78
24
|
return (
|
|
79
25
|
<TabPanel>
|
|
80
|
-
<div className={styles.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
) : cell.id.endsWith('status') ? (
|
|
108
|
-
t(cell.value)
|
|
109
|
-
) : (
|
|
110
|
-
cell.value
|
|
111
|
-
)}
|
|
112
|
-
</TableCell>
|
|
113
|
-
))}
|
|
114
|
-
</TableExpandRow>
|
|
115
|
-
{row.isExpanded ? (
|
|
116
|
-
<TableExpandedRow colSpan={headers.length + 1}>
|
|
117
|
-
<PrescriptionExpanded
|
|
118
|
-
encounterUuid={row.id}
|
|
119
|
-
patientUuid={row.cells.find((cell) => cell.id.endsWith('patient')).value.uuid}
|
|
120
|
-
status={row.cells.find((cell) => cell.id.endsWith('status')).value}
|
|
121
|
-
/>
|
|
122
|
-
</TableExpandedRow>
|
|
123
|
-
) : (
|
|
124
|
-
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
|
|
125
|
-
)}
|
|
126
|
-
</React.Fragment>
|
|
127
|
-
))}
|
|
128
|
-
</TableBody>
|
|
129
|
-
</Table>
|
|
130
|
-
</TableContainer>
|
|
131
|
-
)}
|
|
132
|
-
</DataTable>
|
|
133
|
-
{prescriptionsTableRows?.length === 0 && (
|
|
134
|
-
<div className={styles.filterEmptyState}>
|
|
135
|
-
<Layer>
|
|
136
|
-
<Tile className={styles.filterEmptyStateTile}>
|
|
137
|
-
<p className={styles.filterEmptyStateContent}>
|
|
138
|
-
{t('noPrescriptionsToDisplay', 'No prescriptions to display')}
|
|
139
|
-
</p>
|
|
140
|
-
<p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
|
|
141
|
-
</Tile>
|
|
142
|
-
</Layer>
|
|
143
|
-
</div>
|
|
144
|
-
)}
|
|
145
|
-
{prescriptionsTableRows?.length > 0 && (
|
|
146
|
-
<div style={{ width: '100%' }}>
|
|
147
|
-
<Pagination
|
|
148
|
-
page={page}
|
|
149
|
-
pageSize={pageSize}
|
|
150
|
-
pageSizes={[10, 20, 30, 40, 50, 100]}
|
|
151
|
-
totalItems={totalOrders}
|
|
152
|
-
onChange={({ page, pageSize }) => {
|
|
153
|
-
setPage(page);
|
|
154
|
-
setNextOffSet((page - 1) * pageSize);
|
|
155
|
-
setPageSize(pageSize);
|
|
156
|
-
}}
|
|
157
|
-
/>
|
|
158
|
-
</div>
|
|
159
|
-
)}
|
|
160
|
-
</>
|
|
161
|
-
)}
|
|
26
|
+
<div className={styles.searchContainer}>
|
|
27
|
+
<Search
|
|
28
|
+
closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
|
|
29
|
+
defaultValue={searchTerm}
|
|
30
|
+
placeholder={t('searchByPatientIdOrName', 'Search by patient ID or name')}
|
|
31
|
+
labelText={t('searchByPatientIdOrName', 'Search by patient ID or name')}
|
|
32
|
+
onChange={(e) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
setSearchTerm(e.target.value);
|
|
35
|
+
}}
|
|
36
|
+
size="md"
|
|
37
|
+
className={styles.patientSearch}
|
|
38
|
+
/>
|
|
39
|
+
{config.locationBehavior?.locationFilter?.enabled &&
|
|
40
|
+
!isFilterLocationsLoading &&
|
|
41
|
+
filterLocations?.length > 1 && (
|
|
42
|
+
<ComboBox
|
|
43
|
+
id="locationFilter"
|
|
44
|
+
placeholder={t('filterByLocation', 'Filter by location')}
|
|
45
|
+
items={isFilterLocationsLoading ? [] : filterLocations}
|
|
46
|
+
itemToString={(item: SimpleLocation) => item?.name}
|
|
47
|
+
onChange={({ selectedItem }) => {
|
|
48
|
+
setLocation(selectedItem?.id);
|
|
49
|
+
}}
|
|
50
|
+
className={styles.locationFilter}
|
|
51
|
+
/>
|
|
52
|
+
)}
|
|
162
53
|
</div>
|
|
54
|
+
<PrescriptionsTable
|
|
55
|
+
loadData={isTabActive}
|
|
56
|
+
status={status}
|
|
57
|
+
debouncedSearchTerm={debouncedSearchTerm}
|
|
58
|
+
location={location}
|
|
59
|
+
/>
|
|
163
60
|
</TabPanel>
|
|
164
61
|
);
|
|
165
62
|
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataTable,
|
|
3
|
+
DataTableSkeleton,
|
|
4
|
+
Layer,
|
|
5
|
+
Pagination,
|
|
6
|
+
Table,
|
|
7
|
+
TableBody,
|
|
8
|
+
TableCell,
|
|
9
|
+
TableContainer,
|
|
10
|
+
TableExpandedRow,
|
|
11
|
+
TableExpandHeader,
|
|
12
|
+
TableExpandRow,
|
|
13
|
+
TableHead,
|
|
14
|
+
TableHeader,
|
|
15
|
+
TableRow,
|
|
16
|
+
Tile,
|
|
17
|
+
} from '@carbon/react';
|
|
18
|
+
import { formatDatetime, parseDate, useConfig } from '@openmrs/esm-framework';
|
|
19
|
+
import React, { useEffect, useState } from 'react';
|
|
20
|
+
import { useTranslation } from 'react-i18next';
|
|
21
|
+
import PatientInfoCell from '../patient/patient-info-cell.component';
|
|
22
|
+
import PrescriptionExpanded from './prescription-expanded.component';
|
|
23
|
+
import styles from './prescriptions.scss';
|
|
24
|
+
import { usePrescriptionsTable } from '../medication-request/medication-request.resource';
|
|
25
|
+
import { type PharmacyConfig } from '../config-schema';
|
|
26
|
+
|
|
27
|
+
interface PrescriptionsTableProps {
|
|
28
|
+
loadData: boolean;
|
|
29
|
+
debouncedSearchTerm: string;
|
|
30
|
+
location: string;
|
|
31
|
+
status: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const PrescriptionsTable: React.FC<PrescriptionsTableProps> = ({ loadData, debouncedSearchTerm, location, status }) => {
|
|
35
|
+
const { t } = useTranslation();
|
|
36
|
+
const config = useConfig<PharmacyConfig>();
|
|
37
|
+
const [page, setPage] = useState(1);
|
|
38
|
+
const [pageSize, setPageSize] = useState(10);
|
|
39
|
+
const nextOffSet = (page - 1) * pageSize;
|
|
40
|
+
const { prescriptionsTableRows, error, isLoading, totalOrders } = usePrescriptionsTable(
|
|
41
|
+
loadData,
|
|
42
|
+
pageSize,
|
|
43
|
+
nextOffSet,
|
|
44
|
+
debouncedSearchTerm,
|
|
45
|
+
location,
|
|
46
|
+
status,
|
|
47
|
+
config.medicationRequestExpirationPeriodInDays,
|
|
48
|
+
config.refreshInterval,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// reset back to page 1 whenever search term changes
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
setPage(1);
|
|
54
|
+
}, [debouncedSearchTerm]);
|
|
55
|
+
|
|
56
|
+
// dynamic status keys we need to maintain
|
|
57
|
+
// t('active', 'Active')
|
|
58
|
+
// t('paused', 'Paused')
|
|
59
|
+
// t('closed', 'Closed')
|
|
60
|
+
// t('completed', 'Completed')
|
|
61
|
+
// t('expired', 'Expired')
|
|
62
|
+
// t('cancelled', 'Cancelled')
|
|
63
|
+
|
|
64
|
+
let columns = [
|
|
65
|
+
{ header: t('created', 'Created'), key: 'created' },
|
|
66
|
+
{ header: t('patientName', 'Patient name'), key: 'patient' },
|
|
67
|
+
{ header: t('prescriber', 'Prescriber'), key: 'prescriber' },
|
|
68
|
+
{ header: t('drugs', 'Drugs'), key: 'drugs' },
|
|
69
|
+
{ header: t('lastDispenser', 'Last dispenser'), key: 'lastDispenser' },
|
|
70
|
+
{ header: t('status', 'Status'), key: 'status' },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
// add the locations column, if enabled
|
|
74
|
+
if (config.locationBehavior?.locationColumn?.enabled) {
|
|
75
|
+
columns = [...columns.slice(0, 3), { header: t('location', 'Location'), key: 'location' }, ...columns.slice(3)];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className={styles.patientListTableContainer}>
|
|
80
|
+
{isLoading && <DataTableSkeleton role="progressbar" />}
|
|
81
|
+
{error && <p>Error</p>}
|
|
82
|
+
{prescriptionsTableRows && (
|
|
83
|
+
<>
|
|
84
|
+
<DataTable rows={prescriptionsTableRows} headers={columns} isSortable>
|
|
85
|
+
{({ rows, headers, getExpandHeaderProps, getHeaderProps, getRowProps, getTableProps }) => (
|
|
86
|
+
<TableContainer>
|
|
87
|
+
<Table {...getTableProps()} useZebraStyles>
|
|
88
|
+
<TableHead>
|
|
89
|
+
<TableRow>
|
|
90
|
+
<TableExpandHeader {...getExpandHeaderProps()} />
|
|
91
|
+
{headers.map((header) => (
|
|
92
|
+
<TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader>
|
|
93
|
+
))}
|
|
94
|
+
</TableRow>
|
|
95
|
+
</TableHead>
|
|
96
|
+
<TableBody>
|
|
97
|
+
{rows.map((row) => (
|
|
98
|
+
<React.Fragment key={row.id}>
|
|
99
|
+
<TableExpandRow {...getRowProps({ row })}>
|
|
100
|
+
{row.cells.map((cell) => (
|
|
101
|
+
<TableCell key={cell.id}>
|
|
102
|
+
{cell.id.endsWith('created') ? (
|
|
103
|
+
formatDatetime(parseDate(cell.value))
|
|
104
|
+
) : cell.id.endsWith('patient') ? (
|
|
105
|
+
<PatientInfoCell patient={cell.value} />
|
|
106
|
+
) : cell.id.endsWith('status') ? (
|
|
107
|
+
t(cell.value)
|
|
108
|
+
) : (
|
|
109
|
+
cell.value
|
|
110
|
+
)}
|
|
111
|
+
</TableCell>
|
|
112
|
+
))}
|
|
113
|
+
</TableExpandRow>
|
|
114
|
+
{row.isExpanded ? (
|
|
115
|
+
<TableExpandedRow colSpan={headers.length + 1}>
|
|
116
|
+
<PrescriptionExpanded
|
|
117
|
+
encounterUuid={row.id}
|
|
118
|
+
patientUuid={row.cells.find((cell) => cell.id.endsWith('patient')).value.uuid}
|
|
119
|
+
status={row.cells.find((cell) => cell.id.endsWith('status')).value}
|
|
120
|
+
/>
|
|
121
|
+
</TableExpandedRow>
|
|
122
|
+
) : (
|
|
123
|
+
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
|
|
124
|
+
)}
|
|
125
|
+
</React.Fragment>
|
|
126
|
+
))}
|
|
127
|
+
</TableBody>
|
|
128
|
+
</Table>
|
|
129
|
+
</TableContainer>
|
|
130
|
+
)}
|
|
131
|
+
</DataTable>
|
|
132
|
+
{prescriptionsTableRows?.length === 0 && (
|
|
133
|
+
<div className={styles.filterEmptyState}>
|
|
134
|
+
<Layer>
|
|
135
|
+
<Tile className={styles.filterEmptyStateTile}>
|
|
136
|
+
<p className={styles.filterEmptyStateContent}>
|
|
137
|
+
{t('noPrescriptionsToDisplay', 'No prescriptions to display')}
|
|
138
|
+
</p>
|
|
139
|
+
<p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
|
|
140
|
+
</Tile>
|
|
141
|
+
</Layer>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
{prescriptionsTableRows?.length > 0 && (
|
|
145
|
+
<div style={{ width: '100%' }}>
|
|
146
|
+
<Pagination
|
|
147
|
+
page={page}
|
|
148
|
+
pageSize={pageSize}
|
|
149
|
+
pageSizes={[10, 20, 30, 40, 50, 100]}
|
|
150
|
+
totalItems={totalOrders}
|
|
151
|
+
onChange={({ page, pageSize }) => {
|
|
152
|
+
setPage(page);
|
|
153
|
+
setPageSize(pageSize);
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
</>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export default PrescriptionsTable;
|
package/translations/en.json
CHANGED
|
@@ -96,7 +96,10 @@
|
|
|
96
96
|
"reasonForPause": "Reason for pause",
|
|
97
97
|
"refills": "Refills",
|
|
98
98
|
"route": "Route",
|
|
99
|
+
"search": "Search",
|
|
99
100
|
"searchByPatientIdOrName": "Search by patient ID or name",
|
|
101
|
+
"searchForPatient": "Search for a patient by name or identifier number",
|
|
102
|
+
"searchForPatientHeader": "Search for a patient",
|
|
100
103
|
"selectPrescriptions": "Check prescriptions to print",
|
|
101
104
|
"selectStockDispense": "Select stock to dispense from",
|
|
102
105
|
"status": "Status",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"hash":"a8c56cb22f16ba44","duration":43727}
|
|
Binary file
|