@openmrs/esm-dispensing-app 1.9.1 → 1.9.2-pre.1002
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/.eslintrc +8 -0
- package/.turbo/cache/f95995cd573a4ae0-meta.json +1 -0
- package/.turbo/cache/f95995cd573a4ae0.tar.zst +0 -0
- package/.turbo/turbo-build.log +38 -0
- package/README.md +14 -4
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/1856.js +1 -0
- package/dist/1856.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2690.js +1 -1
- package/dist/2898.js +1 -1
- package/dist/2898.js.map +1 -1
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4099.js +1 -1
- package/dist/4099.js.map +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/445.js +2 -0
- package/dist/445.js.map +1 -0
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4944.js +1 -1
- package/dist/4999.js +2 -0
- package/dist/4999.js.map +1 -0
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5379.js +1 -0
- package/dist/5379.js.map +1 -0
- package/dist/5442.js +1 -1
- package/dist/561.js +1 -0
- package/dist/561.js.map +1 -0
- package/dist/5661.js +1 -1
- package/dist/5897.js +1 -1
- package/dist/5897.js.map +1 -1
- package/dist/6022.js +1 -1
- package/dist/609.js +1 -1
- package/dist/609.js.map +1 -1
- package/dist/6468.js +1 -1
- package/dist/6540.js +1 -1
- package/dist/6540.js.map +1 -1
- package/dist/6679.js +1 -1
- package/dist/67.js +1 -1
- package/dist/67.js.map +1 -1
- package/dist/6840.js +1 -1
- package/dist/6841.js +1 -1
- package/dist/6841.js.map +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7255.js +1 -0
- package/dist/7255.js.map +1 -0
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8600.js +1 -1
- package/dist/8600.js.map +1 -1
- package/dist/8618.js +1 -1
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/961.js +1 -1
- package/dist/961.js.map +1 -1
- package/dist/963.js +1 -1
- package/dist/963.js.map +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.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 +302 -323
- package/dist/openmrs-esm-dispensing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/commands/drug-order-operations.ts +2 -3
- package/e2e/commands/encounter-operations.ts +4 -8
- package/e2e/commands/index.ts +3 -3
- package/e2e/commands/patient-operations.ts +1 -1
- package/e2e/commands/provider-operations.ts +2 -5
- package/e2e/commands/visit-operations.ts +1 -1
- package/e2e/fixtures/api.ts +1 -1
- 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/e2e/support/github/Dockerfile +1 -1
- package/e2e/support/github/run-e2e-docker-env.sh +23 -29
- package/package.json +12 -11
- package/src/components/action-buttons.component.tsx +8 -3
- package/src/components/action-buttons.test.tsx +3 -0
- package/src/components/prescription-actions/close-action-button.component.tsx +3 -1
- package/src/components/prescription-actions/dispense-action-button.component.tsx +3 -1
- package/src/components/prescription-actions/pause-action-button.component.tsx +3 -1
- package/src/dashboard/dispensing-dashboard.component.tsx +3 -0
- package/src/fill-prescription/fill-prescription-button.comonent.tsx +43 -0
- package/src/fill-prescription/fill-prescription-button.scss +8 -0
- package/src/forms/close-dispense-form.workspace.tsx +2 -1
- package/src/forms/dispense-form.workspace.tsx +16 -16
- package/src/forms/forms.scss +15 -0
- package/src/forms/medication-dispense-review.component.tsx +34 -87
- package/src/forms/medication-dispense-review.test.tsx +1 -1
- package/src/forms/pause-dispense-form.workspace.tsx +2 -1
- package/src/history/history-and-comments.component.tsx +2 -0
- package/src/medication-dispense/medication-dispense.resource.tsx +11 -9
- package/src/medication-request/medication-request.resource.test.tsx +7 -3
- package/src/medication-request/medication-request.resource.tsx +47 -44
- 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-details.component.tsx +3 -1
- 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/src/routes.json +2 -2
- package/src/types.ts +10 -1
- package/src/utils.ts +31 -4
- package/tools/setup-tests.ts +7 -0
- package/translations/am.json +3 -0
- package/translations/ar.json +3 -0
- package/translations/ar_SY.json +3 -0
- package/translations/bn.json +3 -0
- package/translations/de.json +3 -0
- package/translations/en.json +3 -0
- package/translations/en_US.json +3 -0
- package/translations/es.json +3 -0
- package/translations/es_MX.json +3 -0
- package/translations/fr.json +3 -0
- package/translations/he.json +3 -0
- package/translations/hi.json +3 -0
- package/translations/hi_IN.json +3 -0
- package/translations/id.json +3 -0
- package/translations/it.json +28 -25
- package/translations/ka.json +3 -0
- package/translations/km.json +3 -0
- package/translations/ku.json +3 -0
- package/translations/ky.json +3 -0
- package/translations/lg.json +3 -0
- package/translations/ne.json +3 -0
- package/translations/pl.json +3 -0
- package/translations/pt.json +3 -0
- package/translations/pt_BR.json +3 -0
- package/translations/qu.json +3 -0
- package/translations/ro_RO.json +3 -0
- package/translations/ru_RU.json +3 -0
- package/translations/si.json +3 -0
- package/translations/sw.json +3 -0
- package/translations/sw_KE.json +3 -0
- package/translations/tr.json +3 -0
- package/translations/tr_TR.json +3 -0
- package/translations/uk.json +3 -0
- package/translations/uz.json +3 -0
- package/translations/uz@Latn.json +3 -0
- package/translations/uz_UZ.json +3 -0
- package/translations/vi.json +3 -0
- package/translations/zh.json +3 -0
- package/translations/zh_CN.json +28 -25
- package/dist/1033.js +0 -1
- package/dist/1033.js.map +0 -1
- package/dist/3414.js +0 -1
- package/dist/3414.js.map +0 -1
- package/dist/6411.js +0 -1
- package/dist/6411.js.map +0 -1
- package/dist/6741.js +0 -2
- package/dist/6741.js.map +0 -1
- package/dist/7268.js +0 -2
- package/dist/7268.js.map +0 -1
- package/dist/7556.js +0 -1
- package/dist/7556.js.map +0 -1
- package/dist/7935.js +0 -1
- package/dist/7935.js.map +0 -1
- package/e2e/commands/medication-request-operation.ts +0 -97
- /package/dist/{7268.js.LICENSE.txt → 445.js.LICENSE.txt} +0 -0
- /package/dist/{6741.js.LICENSE.txt → 4999.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '~@openmrs/esm-styleguide/src/vars' as *;
|
|
3
|
+
|
|
4
|
+
.searchTabPanel {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
margin: layout.$spacing-05 layout.$spacing-04;
|
|
8
|
+
gap: layout.$spacing-05;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.searchForPatientPlaceholder {
|
|
12
|
+
display: flex;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
align-items: center;
|
|
15
|
+
text-align: center;
|
|
16
|
+
min-height: 400px;
|
|
17
|
+
flex: 1;
|
|
18
|
+
border: solid 1px $grey-2;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.searchBar {
|
|
22
|
+
input {
|
|
23
|
+
background-color: $ui-02;
|
|
24
|
+
}
|
|
25
|
+
display: flex;
|
|
26
|
+
}
|
|
@@ -9,7 +9,7 @@ import { type PharmacyConfig } from '../config-schema';
|
|
|
9
9
|
import { PRIVILEGE_CREATE_DISPENSE } from '../constants';
|
|
10
10
|
import { usePatientAllergies, usePrescriptionDetails } from '../medication-request/medication-request.resource';
|
|
11
11
|
import { type AllergyIntolerance, type MedicationRequest, MedicationRequestCombinedStatus } from '../types';
|
|
12
|
-
import { computeMedicationRequestCombinedStatus, getConceptCodingDisplay } from '../utils';
|
|
12
|
+
import { computeMedicationRequestCombinedStatus, getConceptCodingDisplay, useStaleEncounterUuids } from '../utils';
|
|
13
13
|
import PrescriptionsActionsFooter from './prescription-actions.component';
|
|
14
14
|
import styles from './prescription-details.scss';
|
|
15
15
|
|
|
@@ -22,6 +22,7 @@ const PrescriptionDetails: React.FC<{
|
|
|
22
22
|
const [isAllergiesLoading, setAllergiesLoadingStatus] = useState(true);
|
|
23
23
|
const { allergies, totalAllergies } = usePatientAllergies(patientUuid, config.refreshInterval);
|
|
24
24
|
const { medicationRequestBundles, error, isLoading } = usePrescriptionDetails(encounterUuid, config.refreshInterval);
|
|
25
|
+
const { staleEncounterUuids } = useStaleEncounterUuids();
|
|
25
26
|
|
|
26
27
|
useEffect(() => {
|
|
27
28
|
if (typeof totalAllergies == 'number') {
|
|
@@ -99,6 +100,7 @@ const PrescriptionDetails: React.FC<{
|
|
|
99
100
|
patientUuid={patientUuid}
|
|
100
101
|
encounterUuid={encounterUuid}
|
|
101
102
|
medicationRequestBundle={bundle}
|
|
103
|
+
disabled={staleEncounterUuids.includes(encounterUuid)}
|
|
102
104
|
/>
|
|
103
105
|
</UserHasAccess>
|
|
104
106
|
<MedicationEvent
|
|
@@ -1,45 +1,14 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Tab, Tabs, TabList, TabPanels } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
5
|
-
import { useLocationForFiltering } from '../location/location.resource';
|
|
6
|
-
import { type SimpleLocation } from '../types';
|
|
7
|
-
import { type PharmacyConfig } from '../config-schema';
|
|
8
4
|
import PrescriptionTabPanel from './prescription-tab-panel.component';
|
|
9
5
|
import styles from './prescriptions.scss';
|
|
6
|
+
import PatientSearchTabPanel from './patient-search-tab-panel.component';
|
|
10
7
|
|
|
11
8
|
const PrescriptionTabLists: React.FC = () => {
|
|
12
9
|
const { t } = useTranslation();
|
|
13
|
-
const config = useConfig<PharmacyConfig>();
|
|
14
|
-
const { filterLocations, isLoading: isFilterLocationsLoading } = useLocationForFiltering(config);
|
|
15
|
-
const [searchTermUserInput, setSearchTermUserInput] = useState(''); // we have a separate "searchTermUserInput" and "searchTerm" in order to debounce
|
|
16
|
-
const [searchTerm, setSearchTerm] = useState('');
|
|
17
|
-
const [location, setLocation] = useState('');
|
|
18
10
|
const [selectedTab, setSelectedTab] = useState(0);
|
|
19
11
|
|
|
20
|
-
const tabs = [
|
|
21
|
-
{
|
|
22
|
-
key: 'activePrescriptions',
|
|
23
|
-
header: t('activePrescriptions', 'Active Prescriptions'),
|
|
24
|
-
status: 'ACTIVE',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
key: 'allPrescriptions',
|
|
28
|
-
header: t('allPrescriptions', 'All Prescriptions'),
|
|
29
|
-
status: '',
|
|
30
|
-
},
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
// debounce: delay the search term update so that a search isn't triggered on every single keystroke
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
const debounceFn = setTimeout(() => {
|
|
36
|
-
setSearchTerm(searchTermUserInput);
|
|
37
|
-
}, 500);
|
|
38
|
-
|
|
39
|
-
return () => clearTimeout(debounceFn);
|
|
40
|
-
}, [searchTermUserInput]);
|
|
41
|
-
|
|
42
|
-
// we use this to only render the tab panel that is currently selected, see O3-2777
|
|
43
12
|
const handleTabChange = (event) => {
|
|
44
13
|
setSelectedTab(event.selectedIndex);
|
|
45
14
|
};
|
|
@@ -49,58 +18,23 @@ const PrescriptionTabLists: React.FC = () => {
|
|
|
49
18
|
<section className={styles.prescriptionTabsContainer}>
|
|
50
19
|
<Tabs onChange={handleTabChange}>
|
|
51
20
|
<TabList aria-label={t('tabList', 'Tab List')} contained className={styles.tabsContainer}>
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
21
|
+
<Tab title={t('search', 'Search')} id={'tab-search'} className={styles.tab}>
|
|
22
|
+
{t('search', 'Search')}
|
|
23
|
+
</Tab>
|
|
24
|
+
<Tab
|
|
25
|
+
title={t('activePrescriptions', 'Active Prescriptions')}
|
|
26
|
+
id={'tab-active-prescription'}
|
|
27
|
+
className={styles.tab}>
|
|
28
|
+
{t('activePrescriptions', 'Active Prescriptions')}
|
|
29
|
+
</Tab>
|
|
30
|
+
<Tab title={t('allPrescriptions', 'All Prescriptions')} id={'tab-all-prescription'} className={styles.tab}>
|
|
31
|
+
{t('allPrescriptions', 'All Prescriptions')}
|
|
32
|
+
</Tab>
|
|
59
33
|
</TabList>
|
|
60
|
-
<div className={styles.searchContainer}>
|
|
61
|
-
{/* <Button
|
|
62
|
-
kind="primary"
|
|
63
|
-
renderIcon={(props) => <Add size={24} />}
|
|
64
|
-
className={styles.addPrescriptionBtn}
|
|
65
|
-
size="sm"
|
|
66
|
-
>
|
|
67
|
-
{t("fillPrescription", "Fill prescription")}
|
|
68
|
-
</Button>*/}
|
|
69
|
-
<Search
|
|
70
|
-
closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
|
|
71
|
-
defaultValue={searchTermUserInput}
|
|
72
|
-
placeholder={t('searchByPatientIdOrName', 'Search by patient ID or name')}
|
|
73
|
-
labelText={t('searchByPatientIdOrName', 'Search by patient ID or name')}
|
|
74
|
-
onChange={(e) => {
|
|
75
|
-
e.preventDefault();
|
|
76
|
-
setSearchTermUserInput(e.target.value);
|
|
77
|
-
}}
|
|
78
|
-
size="md"
|
|
79
|
-
className={styles.patientSearch}
|
|
80
|
-
/>
|
|
81
|
-
{config.locationBehavior?.locationFilter?.enabled &&
|
|
82
|
-
!isFilterLocationsLoading &&
|
|
83
|
-
filterLocations?.length > 1 && (
|
|
84
|
-
<ComboBox
|
|
85
|
-
id="locationFilter"
|
|
86
|
-
placeholder={t('filterByLocation', 'Filter by location')}
|
|
87
|
-
items={isFilterLocationsLoading ? [] : filterLocations}
|
|
88
|
-
itemToString={(item: SimpleLocation) => item?.name}
|
|
89
|
-
onChange={({ selectedItem }) => {
|
|
90
|
-
setLocation(selectedItem?.id);
|
|
91
|
-
}}
|
|
92
|
-
className={styles.locationFilter}
|
|
93
|
-
/>
|
|
94
|
-
)}
|
|
95
|
-
</div>
|
|
96
34
|
<TabPanels>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
) : (
|
|
101
|
-
<></>
|
|
102
|
-
);
|
|
103
|
-
})}
|
|
35
|
+
<PatientSearchTabPanel />
|
|
36
|
+
<PrescriptionTabPanel isTabActive={selectedTab === 1} status={'ACTIVE'} />
|
|
37
|
+
<PrescriptionTabPanel isTabActive={selectedTab === 2} status={''} />
|
|
104
38
|
</TabPanels>
|
|
105
39
|
</Tabs>
|
|
106
40
|
</section>
|
|
@@ -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/src/routes.json
CHANGED
package/src/types.ts
CHANGED
|
@@ -451,7 +451,7 @@ export interface PrescriptionsTableRow {
|
|
|
451
451
|
|
|
452
452
|
export interface Quantity {
|
|
453
453
|
value: number;
|
|
454
|
-
unit
|
|
454
|
+
unit?: string;
|
|
455
455
|
code: string;
|
|
456
456
|
system?: string;
|
|
457
457
|
}
|
|
@@ -523,3 +523,12 @@ export type StockDispenseRequest = {
|
|
|
523
523
|
stockItemPackagingUOM: string;
|
|
524
524
|
quantity: number;
|
|
525
525
|
};
|
|
526
|
+
|
|
527
|
+
export interface DispensingStore {
|
|
528
|
+
/**
|
|
529
|
+
* A list of encounterUuids with stale data due to
|
|
530
|
+
* updates (like pausing of dispensing medication) actions,
|
|
531
|
+
* and subsequent SWR revalidation has not yet completed
|
|
532
|
+
*/
|
|
533
|
+
staleEncounterUuids: string[];
|
|
534
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -2,6 +2,7 @@ import dayjs from 'dayjs';
|
|
|
2
2
|
import { mutate } from 'swr';
|
|
3
3
|
import {
|
|
4
4
|
type Coding,
|
|
5
|
+
type DispensingStore,
|
|
5
6
|
type DosageInstruction,
|
|
6
7
|
type Medication,
|
|
7
8
|
type MedicationDispense,
|
|
@@ -14,7 +15,12 @@ import {
|
|
|
14
15
|
MedicationRequestStatus,
|
|
15
16
|
type Quantity,
|
|
16
17
|
} from './types';
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
createGlobalStore,
|
|
20
|
+
fhirBaseUrl,
|
|
21
|
+
parseDate,
|
|
22
|
+
useStore,
|
|
23
|
+
} from '@openmrs/esm-framework';
|
|
18
24
|
import {
|
|
19
25
|
OPENMRS_FHIR_EXT_DISPENSE_RECORDED,
|
|
20
26
|
OPENMRS_FHIR_EXT_MEDICINE,
|
|
@@ -565,16 +571,29 @@ export function isMostRecentMedicationDispense(
|
|
|
565
571
|
|
|
566
572
|
/**
|
|
567
573
|
* Revalidated (reloads) both the prescription associated with the encounter uuid,
|
|
568
|
-
* and the entire
|
|
574
|
+
* and the entire prescription table
|
|
569
575
|
* @param encounterUuid
|
|
570
576
|
*/
|
|
571
|
-
export function revalidate(encounterUuid: string) {
|
|
572
|
-
mutate(
|
|
577
|
+
export async function revalidate(encounterUuid: string) {
|
|
578
|
+
await mutate(
|
|
573
579
|
(key) =>
|
|
574
580
|
typeof key === 'string' &&
|
|
575
581
|
(key.startsWith(`${fhirBaseUrl}/${PRESCRIPTIONS_TABLE_ENDPOINT}`) ||
|
|
576
582
|
key.startsWith(`${fhirBaseUrl}/${PRESCRIPTION_DETAILS_ENDPOINT}?encounter=${encounterUuid}`)),
|
|
577
583
|
);
|
|
584
|
+
dispensingStore.setState((state) => ({
|
|
585
|
+
staleEncounterUuids: state.staleEncounterUuids.filter((uuid) => uuid !== encounterUuid),
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Mark the specified encounter as stale. The encounter will be unmarked on calling
|
|
591
|
+
* revalidate() to reload the data associated with the encounter.
|
|
592
|
+
*/
|
|
593
|
+
export function markEncounterAsStale(encounterUuid: string) {
|
|
594
|
+
dispensingStore.setState((state) => ({
|
|
595
|
+
staleEncounterUuids: [...state.staleEncounterUuids, encounterUuid],
|
|
596
|
+
}));
|
|
578
597
|
}
|
|
579
598
|
|
|
580
599
|
export function sortMedicationDispensesByWhenHandedOver(a: MedicationDispense, b: MedicationDispense): number {
|
|
@@ -590,3 +609,11 @@ export function sortMedicationDispensesByWhenHandedOver(a: MedicationDispense, b
|
|
|
590
609
|
return a.id.localeCompare(b.id); // just to enforce a standard order if two dates are equals
|
|
591
610
|
}
|
|
592
611
|
}
|
|
612
|
+
|
|
613
|
+
const dispensingStore = createGlobalStore<DispensingStore>('dispensing-store', {
|
|
614
|
+
staleEncounterUuids: [],
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
export function useStaleEncounterUuids() {
|
|
618
|
+
return useStore(dispensingStore);
|
|
619
|
+
}
|