@ampath/esm-dispensing-app 1.10.0-next.1
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/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +80 -0
- package/.husky/pre-commit +7 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +12 -0
- package/.tx/config +11 -0
- package/.yarn/versions/1c40b9b6.yml +0 -0
- package/.yarn/versions/ff162597.yml +0 -0
- package/LICENSE +401 -0
- package/README.md +124 -0
- package/__mocks__/react-i18next.js +51 -0
- package/dist/1043.js +1 -0
- package/dist/1043.js.map +1 -0
- package/dist/1119.js +1 -0
- package/dist/1197.js +1 -0
- package/dist/2146.js +1 -0
- package/dist/2177.js +2 -0
- package/dist/2177.js.LICENSE.txt +9 -0
- package/dist/2177.js.map +1 -0
- package/dist/2690.js +1 -0
- package/dist/2890.js +2 -0
- package/dist/2890.js.LICENSE.txt +14 -0
- package/dist/2890.js.map +1 -0
- package/dist/2898.js +1 -0
- package/dist/2898.js.map +1 -0
- package/dist/3041.js +1 -0
- package/dist/3041.js.map +1 -0
- package/dist/3099.js +1 -0
- package/dist/3184.js +2 -0
- package/dist/3184.js.LICENSE.txt +14 -0
- package/dist/3184.js.map +1 -0
- package/dist/3568.js +1 -0
- package/dist/3568.js.map +1 -0
- package/dist/3584.js +1 -0
- package/dist/4055.js +1 -0
- package/dist/4099.js +1 -0
- package/dist/4099.js.map +1 -0
- package/dist/4132.js +1 -0
- package/dist/4225.js +1 -0
- package/dist/4225.js.map +1 -0
- package/dist/4300.js +1 -0
- package/dist/4335.js +1 -0
- package/dist/4353.js +1 -0
- package/dist/4353.js.map +1 -0
- package/dist/439.js +1 -0
- package/dist/4618.js +1 -0
- package/dist/4652.js +1 -0
- package/dist/4944.js +1 -0
- package/dist/5173.js +1 -0
- package/dist/5241.js +1 -0
- package/dist/5422.js +1 -0
- package/dist/5422.js.map +1 -0
- package/dist/5442.js +1 -0
- package/dist/5661.js +1 -0
- package/dist/5897.js +1 -0
- package/dist/5897.js.map +1 -0
- package/dist/6022.js +1 -0
- package/dist/609.js +1 -0
- package/dist/609.js.map +1 -0
- package/dist/6468.js +1 -0
- package/dist/6540.js +2 -0
- package/dist/6540.js.LICENSE.txt +9 -0
- package/dist/6540.js.map +1 -0
- package/dist/6589.js +1 -0
- package/dist/6606.js +1 -0
- package/dist/6606.js.map +1 -0
- package/dist/6679.js +1 -0
- package/dist/6825.js +1 -0
- package/dist/6825.js.map +1 -0
- package/dist/6840.js +1 -0
- package/dist/6841.js +1 -0
- package/dist/6841.js.map +1 -0
- package/dist/6859.js +1 -0
- package/dist/7097.js +1 -0
- package/dist/7159.js +1 -0
- package/dist/723.js +1 -0
- package/dist/7240.js +1 -0
- package/dist/7240.js.map +1 -0
- package/dist/7255.js +1 -0
- package/dist/7255.js.map +1 -0
- package/dist/7617.js +1 -0
- package/dist/795.js +1 -0
- package/dist/8163.js +1 -0
- package/dist/8349.js +1 -0
- package/dist/8371.js +1 -0
- package/dist/8569.js +2 -0
- package/dist/8569.js.LICENSE.txt +41 -0
- package/dist/8569.js.map +1 -0
- package/dist/8600.js +1 -0
- package/dist/8600.js.map +1 -0
- package/dist/8618.js +1 -0
- package/dist/8885.js +1 -0
- package/dist/8885.js.map +1 -0
- package/dist/890.js +1 -0
- package/dist/9214.js +1 -0
- package/dist/9538.js +1 -0
- package/dist/9569.js +1 -0
- package/dist/961.js +2 -0
- package/dist/961.js.LICENSE.txt +19 -0
- package/dist/961.js.map +1 -0
- package/dist/963.js +1 -0
- package/dist/963.js.map +1 -0
- package/dist/986.js +1 -0
- package/dist/9879.js +1 -0
- package/dist/9895.js +1 -0
- package/dist/9900.js +1 -0
- package/dist/9913.js +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +51 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-dispensing-app.js +1 -0
- package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +1645 -0
- package/dist/openmrs-esm-dispensing-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/e2e/README.md +119 -0
- package/e2e/commands/drug-order-operations.ts +43 -0
- package/e2e/commands/encounter-operations.ts +60 -0
- package/e2e/commands/index.ts +5 -0
- package/e2e/commands/patient-operations.ts +109 -0
- package/e2e/commands/provider-operations.ts +9 -0
- package/e2e/commands/types/index.ts +157 -0
- package/e2e/commands/visit-operations.ts +38 -0
- package/e2e/core/global-setup.ts +32 -0
- package/e2e/core/index.ts +1 -0
- package/e2e/core/test.ts +31 -0
- package/e2e/fixtures/api.ts +27 -0
- package/e2e/fixtures/index.ts +1 -0
- package/e2e/pages/dispensing-page.ts +9 -0
- package/e2e/pages/index.ts +1 -0
- package/e2e/specs/active-prescriptions.spec.ts +72 -0
- package/e2e/specs/close-prescription.spec.ts +71 -0
- package/e2e/specs/dispense-medication.spec.ts +71 -0
- package/e2e/specs/pause-prescription.spec.ts +72 -0
- package/e2e/support/github/Dockerfile +34 -0
- package/e2e/support/github/docker-compose.yml +24 -0
- package/e2e/support/github/run-e2e-docker-env.sh +42 -0
- package/e2e/types/index.ts +157 -0
- package/example.env +7 -0
- package/jest.config.js +24 -0
- package/package.json +110 -0
- package/playwright.config.ts +36 -0
- package/prettier.config.js +8 -0
- package/src/components/action-buttons.component.tsx +87 -0
- package/src/components/action-buttons.scss +16 -0
- package/src/components/action-buttons.test.tsx +217 -0
- package/src/components/medication-card.component.tsx +37 -0
- package/src/components/medication-card.scss +20 -0
- package/src/components/medication-card.test.tsx +36 -0
- package/src/components/medication-dispense-review.scss +108 -0
- package/src/components/medication-event.component.tsx +96 -0
- package/src/components/medication-event.scss +44 -0
- package/src/components/medication-event.test.tsx +212 -0
- package/src/components/prescription-actions/close-action-button.component.tsx +50 -0
- package/src/components/prescription-actions/dispense-action-button.component.tsx +57 -0
- package/src/components/prescription-actions/pause-action-button.component.tsx +49 -0
- package/src/conditions/conditions.component.tsx +118 -0
- package/src/conditions/conditions.resource.ts +100 -0
- package/src/conditions/conditions.scss +26 -0
- package/src/conditions/conditions.test.tsx +200 -0
- package/src/config-schema.ts +192 -0
- package/src/constants.ts +22 -0
- package/src/dashboard/dispensing-dashboard-link.component.tsx +36 -0
- package/src/dashboard/dispensing-dashboard.component.tsx +36 -0
- package/src/declarations.d.ts +2 -0
- package/src/diagnoses/diagnoses.component.tsx +111 -0
- package/src/diagnoses/diagnoses.resource.ts +30 -0
- package/src/diagnoses/diagnoses.scss +31 -0
- package/src/dispensing-link.component.tsx +9 -0
- package/src/dispensing-tiles/dispensing-tile.component.tsx +42 -0
- package/src/dispensing-tiles/dispensing-tile.scss +43 -0
- package/src/dispensing-tiles/dispensing-tiles.component.tsx +39 -0
- package/src/dispensing-tiles/dispensing-tiles.resource.tsx +30 -0
- package/src/dispensing-tiles/dispensing-tiles.scss +11 -0
- package/src/dispensing.component.tsx +31 -0
- package/src/dispensing.scss +5 -0
- package/src/dispensing.test.tsx +9 -0
- package/src/fill-prescription/fill-prescription-button.component.tsx +103 -0
- package/src/fill-prescription/fill-prescription-button.scss +8 -0
- package/src/fill-prescription/on-prescription-filled.modal.tsx +140 -0
- package/src/fill-prescription/on-prescription-filled.scss +7 -0
- package/src/forms/close-dispense-form.workspace.tsx +194 -0
- package/src/forms/dispense-form.workspace.test.tsx +334 -0
- package/src/forms/dispense-form.workspace.tsx +324 -0
- package/src/forms/forms.scss +152 -0
- package/src/forms/medication-dispense-review.component.tsx +649 -0
- package/src/forms/medication-dispense-review.test.tsx +158 -0
- package/src/forms/pause-dispense-form.workspace.tsx +196 -0
- package/src/forms/stock-dispense/stock-dispense.component.tsx +126 -0
- package/src/forms/stock-dispense/stock.resource.tsx +67 -0
- package/src/history/delete-confirm.modal.tsx +35 -0
- package/src/history/history-and-comments.component.tsx +338 -0
- package/src/history/history-and-comments.scss +54 -0
- package/src/index.ts +57 -0
- package/src/location/location.resource.test.tsx +108 -0
- package/src/location/location.resource.tsx +32 -0
- package/src/medication/medication.resource.test.tsx +156 -0
- package/src/medication/medication.resource.tsx +45 -0
- package/src/medication-dispense/medication-dispense.resource.test.tsx +243 -0
- package/src/medication-dispense/medication-dispense.resource.tsx +178 -0
- package/src/medication-request/medication-request.resource.test.tsx +1333 -0
- package/src/medication-request/medication-request.resource.tsx +257 -0
- package/src/patient/patient-info-cell.component.tsx +26 -0
- package/src/patient/patient.resources.ts +14 -0
- package/src/pharmacy-header/pharmacy-header.component.tsx +35 -0
- package/src/pharmacy-header/pharmacy-header.scss +55 -0
- package/src/pharmacy-header/pharmacy-illustration.component.tsx +30 -0
- 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-actions.component.tsx +24 -0
- package/src/prescriptions/prescription-actions.scss +14 -0
- package/src/prescriptions/prescription-details.component.tsx +152 -0
- package/src/prescriptions/prescription-details.scss +87 -0
- package/src/prescriptions/prescription-details.test.tsx +267 -0
- package/src/prescriptions/prescription-expanded.component.tsx +58 -0
- package/src/prescriptions/prescription-expanded.scss +56 -0
- package/src/prescriptions/prescription-tab-lists.component.tsx +70 -0
- package/src/prescriptions/prescription-tab-panel.component.tsx +83 -0
- package/src/prescriptions/prescriptions-table.component.tsx +189 -0
- package/src/prescriptions/prescriptions.scss +152 -0
- package/src/print-prescription/prescription-print-action.component.tsx +30 -0
- package/src/print-prescription/prescription-print-preview.modal.tsx +92 -0
- package/src/print-prescription/prescription-printout.component.tsx +154 -0
- package/src/print-prescription/print-prescription.scss +75 -0
- package/src/print-prescription/printable-prescriptions.component.tsx +57 -0
- package/src/routes.json +137 -0
- package/src/types.ts +530 -0
- package/src/utils.test.ts +2947 -0
- package/src/utils.ts +637 -0
- package/tools/i18next-parser.config.js +89 -0
- package/tools/setup-tests.ts +8 -0
- package/tools/update-openmrs-deps.mjs +42 -0
- package/translations/am.json +133 -0
- package/translations/ar.json +133 -0
- package/translations/ar_SY.json +133 -0
- package/translations/bn.json +133 -0
- package/translations/cs.json +133 -0
- package/translations/de.json +133 -0
- package/translations/en.json +133 -0
- package/translations/en_US.json +133 -0
- package/translations/es.json +133 -0
- package/translations/es_MX.json +133 -0
- package/translations/fr.json +133 -0
- package/translations/he.json +133 -0
- package/translations/hi.json +133 -0
- package/translations/hi_IN.json +133 -0
- package/translations/id.json +133 -0
- package/translations/it.json +133 -0
- package/translations/ka.json +133 -0
- package/translations/km.json +133 -0
- package/translations/ku.json +133 -0
- package/translations/ky.json +133 -0
- package/translations/lg.json +133 -0
- package/translations/ne.json +133 -0
- package/translations/pl.json +133 -0
- package/translations/pt.json +133 -0
- package/translations/pt_BR.json +133 -0
- package/translations/qu.json +133 -0
- package/translations/ro_RO.json +133 -0
- package/translations/ru_RU.json +133 -0
- package/translations/si.json +133 -0
- package/translations/sq.json +133 -0
- package/translations/sw.json +133 -0
- package/translations/sw_KE.json +133 -0
- package/translations/tr.json +133 -0
- package/translations/tr_TR.json +133 -0
- package/translations/uk.json +133 -0
- package/translations/uz.json +133 -0
- package/translations/uz@Latn.json +133 -0
- package/translations/uz_UZ.json +133 -0
- package/translations/vi.json +133 -0
- package/translations/zh.json +133 -0
- package/translations/zh_CN.json +133 -0
- package/translations/zh_TW.json +133 -0
- package/tsconfig.json +23 -0
- package/turbo.json +41 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import useSWR from 'swr';
|
|
4
|
+
import { fhirBaseUrl, openmrsFetch, parseDate } from '@openmrs/esm-framework';
|
|
5
|
+
import { JSON_MERGE_PATH_MIME_TYPE, OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS } from '../constants';
|
|
6
|
+
import {
|
|
7
|
+
type AllergyIntoleranceResponse,
|
|
8
|
+
type EncounterResponse,
|
|
9
|
+
type MedicationRequest,
|
|
10
|
+
type MedicationRequestResponse,
|
|
11
|
+
type PrescriptionsTableRow,
|
|
12
|
+
type MedicationDispense,
|
|
13
|
+
type Encounter,
|
|
14
|
+
type MedicationRequestFulfillerStatus,
|
|
15
|
+
type MedicationRequestBundle,
|
|
16
|
+
type SimpleLocation,
|
|
17
|
+
} from '../types';
|
|
18
|
+
import {
|
|
19
|
+
getPrescriptionDetailsEndpoint,
|
|
20
|
+
getMedicationDisplay,
|
|
21
|
+
getMedicationReferenceOrCodeableConcept,
|
|
22
|
+
getPrescriptionTableEndpoint,
|
|
23
|
+
sortMedicationDispensesByWhenHandedOver,
|
|
24
|
+
computePrescriptionStatusMessageCode,
|
|
25
|
+
getAssociatedMedicationDispenses,
|
|
26
|
+
} from '../utils';
|
|
27
|
+
|
|
28
|
+
export function usePrescriptionsTable(
|
|
29
|
+
loadData: boolean,
|
|
30
|
+
customPrescriptionsTableEndpoint: string = '',
|
|
31
|
+
status: string = '',
|
|
32
|
+
pageSize: number = 10,
|
|
33
|
+
pageOffset: number = 0,
|
|
34
|
+
patientSearchTerm: string = '',
|
|
35
|
+
locations: SimpleLocation[] = [],
|
|
36
|
+
medicationRequestExpirationPeriodInDays: number,
|
|
37
|
+
refreshInterval: number,
|
|
38
|
+
) {
|
|
39
|
+
const { data, error } = useSWR<{ data: EncounterResponse }, Error>(
|
|
40
|
+
loadData
|
|
41
|
+
? getPrescriptionTableEndpoint(
|
|
42
|
+
customPrescriptionsTableEndpoint,
|
|
43
|
+
status,
|
|
44
|
+
pageOffset,
|
|
45
|
+
pageSize,
|
|
46
|
+
dayjs(new Date()).startOf('day').subtract(medicationRequestExpirationPeriodInDays, 'day').toISOString(),
|
|
47
|
+
patientSearchTerm,
|
|
48
|
+
locations?.map((location) => location.id).join(','),
|
|
49
|
+
)
|
|
50
|
+
: null,
|
|
51
|
+
openmrsFetch,
|
|
52
|
+
{ refreshInterval: refreshInterval },
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
let prescriptionsTableRows: PrescriptionsTableRow[];
|
|
56
|
+
if (data) {
|
|
57
|
+
const entries = data?.data.entry;
|
|
58
|
+
if (entries) {
|
|
59
|
+
const encounters = entries
|
|
60
|
+
.filter((entry) => entry?.resource?.resourceType == 'Encounter')
|
|
61
|
+
.map((entry) => entry.resource as Encounter);
|
|
62
|
+
const medicationRequests = entries
|
|
63
|
+
.filter((entry) => entry?.resource?.resourceType == 'MedicationRequest')
|
|
64
|
+
.map((entry) => entry.resource as MedicationRequest);
|
|
65
|
+
const medicationDispenses = entries
|
|
66
|
+
.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
|
|
67
|
+
.map((entry) => entry.resource as MedicationDispense)
|
|
68
|
+
.sort(sortMedicationDispensesByWhenHandedOver);
|
|
69
|
+
prescriptionsTableRows = encounters.map((encounter) => {
|
|
70
|
+
const medicationRequestsForEncounter = medicationRequests.filter(
|
|
71
|
+
(medicationRequest) => medicationRequest.encounter.reference == 'Encounter/' + encounter.id,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const medicationRequestReferences = medicationRequestsForEncounter.map(
|
|
75
|
+
(medicationRequest) => 'MedicationRequest/' + medicationRequest.id,
|
|
76
|
+
);
|
|
77
|
+
const medicationDispensesForMedicationRequests = medicationDispenses.filter((medicationDispense) =>
|
|
78
|
+
medicationRequestReferences.includes(medicationDispense.authorizingPrescription[0]?.reference),
|
|
79
|
+
);
|
|
80
|
+
return buildPrescriptionsTableRow(
|
|
81
|
+
encounter,
|
|
82
|
+
medicationRequestsForEncounter,
|
|
83
|
+
medicationDispensesForMedicationRequests,
|
|
84
|
+
medicationRequestExpirationPeriodInDays,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
prescriptionsTableRows.sort((a, b) => (a.created < b.created ? 1 : -1));
|
|
88
|
+
} else {
|
|
89
|
+
prescriptionsTableRows = [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
prescriptionsTableRows,
|
|
95
|
+
error: error,
|
|
96
|
+
isLoading: !prescriptionsTableRows && !error,
|
|
97
|
+
totalOrders: data?.data.total,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildPrescriptionsTableRow(
|
|
102
|
+
encounter: Encounter,
|
|
103
|
+
medicationRequests: Array<MedicationRequest>,
|
|
104
|
+
medicationDispense: Array<MedicationDispense>,
|
|
105
|
+
medicationRequestExpirationPeriodInDays: number,
|
|
106
|
+
): PrescriptionsTableRow {
|
|
107
|
+
return {
|
|
108
|
+
id: encounter?.id,
|
|
109
|
+
created: encounter?.period?.start,
|
|
110
|
+
patient: {
|
|
111
|
+
name: encounter?.subject?.display,
|
|
112
|
+
uuid: encounter?.subject?.reference?.split('/')[1],
|
|
113
|
+
},
|
|
114
|
+
drugs: [
|
|
115
|
+
...new Set(
|
|
116
|
+
medicationRequests
|
|
117
|
+
.map((medicationRequest) => getMedicationDisplay(getMedicationReferenceOrCodeableConcept(medicationRequest)))
|
|
118
|
+
.sort((a, b) => {
|
|
119
|
+
return a.localeCompare(b);
|
|
120
|
+
}),
|
|
121
|
+
),
|
|
122
|
+
].join('; '),
|
|
123
|
+
lastDispenser:
|
|
124
|
+
medicationDispense && medicationDispense[0]?.performer && medicationDispense[0]?.performer[0]?.actor.display,
|
|
125
|
+
prescriber: [...new Set(medicationRequests.map((o) => o.requester.display))].join(', '),
|
|
126
|
+
status: computePrescriptionStatusMessageCode(medicationRequests, medicationRequestExpirationPeriodInDays),
|
|
127
|
+
location: encounter?.location ? encounter?.location[0]?.location.display : null,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function usePrescriptionDetails(encounterUuid: string, refreshInterval = null) {
|
|
132
|
+
const { data, ...rest } = useSWR<{ data: MedicationRequestResponse }, Error>(
|
|
133
|
+
getPrescriptionDetailsEndpoint(encounterUuid),
|
|
134
|
+
openmrsFetch,
|
|
135
|
+
{ refreshInterval: refreshInterval },
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const { medicationRequestBundles, prescriptionDate } = useMemo(() => {
|
|
139
|
+
if (data) {
|
|
140
|
+
return medicationRequestResponseToPrescriptionDetails(data.data.entry);
|
|
141
|
+
} else {
|
|
142
|
+
return { medicationRequestBundles: [], prescriptionDate: null };
|
|
143
|
+
}
|
|
144
|
+
}, [data]);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
medicationRequestBundles,
|
|
148
|
+
prescriptionDate,
|
|
149
|
+
...rest,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* fetches prescription details of a given encounter directly via openmrsFetch (instead of useSWR)
|
|
155
|
+
* @param encounterUuid
|
|
156
|
+
* @returns
|
|
157
|
+
*/
|
|
158
|
+
export async function getPrescriptionDetails(encounterUuid: string) {
|
|
159
|
+
const result = await openmrsFetch<MedicationRequestResponse>(getPrescriptionDetailsEndpoint(encounterUuid));
|
|
160
|
+
const {
|
|
161
|
+
data: { entry },
|
|
162
|
+
} = result;
|
|
163
|
+
return medicationRequestResponseToPrescriptionDetails(entry);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function medicationRequestResponseToPrescriptionDetails(
|
|
167
|
+
results: { resource: MedicationRequest | MedicationDispense }[],
|
|
168
|
+
) {
|
|
169
|
+
const medicationRequestBundles: Array<MedicationRequestBundle> = [];
|
|
170
|
+
let prescriptionDate: Date;
|
|
171
|
+
|
|
172
|
+
const encounter = results
|
|
173
|
+
?.filter((entry) => entry?.resource?.resourceType == 'Encounter')
|
|
174
|
+
.map((entry) => entry.resource as Encounter);
|
|
175
|
+
|
|
176
|
+
if (encounter) {
|
|
177
|
+
// by definition of the request (search by encounter) there should be one and only one encounter
|
|
178
|
+
prescriptionDate = parseDate(encounter[0]?.period.start);
|
|
179
|
+
|
|
180
|
+
const medicationRequests = results
|
|
181
|
+
?.filter((entry) => entry?.resource?.resourceType == 'MedicationRequest')
|
|
182
|
+
.map((entry) => entry.resource as MedicationRequest);
|
|
183
|
+
|
|
184
|
+
const medicationDispenses = results
|
|
185
|
+
?.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
|
|
186
|
+
.map((entry) => entry.resource as MedicationDispense)
|
|
187
|
+
.sort(sortMedicationDispensesByWhenHandedOver);
|
|
188
|
+
|
|
189
|
+
medicationRequests.every((medicationRequest) =>
|
|
190
|
+
medicationRequestBundles.push({
|
|
191
|
+
request: medicationRequest,
|
|
192
|
+
dispenses: getAssociatedMedicationDispenses(medicationRequest, medicationDispenses).sort(
|
|
193
|
+
sortMedicationDispensesByWhenHandedOver,
|
|
194
|
+
),
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { medicationRequestBundles, prescriptionDate };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function usePatientAllergies(patientUuid: string, refreshInterval) {
|
|
203
|
+
const { data, error, isLoading } = useSWR<{ data: AllergyIntoleranceResponse }, Error>(
|
|
204
|
+
`${fhirBaseUrl}/AllergyIntolerance?patient=${patientUuid}`,
|
|
205
|
+
openmrsFetch,
|
|
206
|
+
{ refreshInterval: refreshInterval },
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const allergies = data?.data.entry?.map((allergy) => allergy.resource) ?? [];
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
allergies,
|
|
213
|
+
totalAllergies: data?.data.total,
|
|
214
|
+
error,
|
|
215
|
+
isLoading,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// supports passing just the uuid/code or the entire reference, ie either: "MedicationReference/123-abc" or "123-abc"
|
|
220
|
+
export function useMedicationRequest(reference: string, refreshInterval) {
|
|
221
|
+
reference = reference
|
|
222
|
+
? reference.startsWith('MedicationRequest')
|
|
223
|
+
? reference
|
|
224
|
+
: `MedicationRequest/${reference}`
|
|
225
|
+
: null;
|
|
226
|
+
|
|
227
|
+
const { data } = useSWR<{ data: MedicationRequest }, Error>(
|
|
228
|
+
reference ? `${fhirBaseUrl}/${reference}` : null,
|
|
229
|
+
openmrsFetch,
|
|
230
|
+
{ refreshInterval: refreshInterval },
|
|
231
|
+
);
|
|
232
|
+
return {
|
|
233
|
+
medicationRequest: data ? data.data : null,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function updateMedicationRequestFulfillerStatus(
|
|
238
|
+
medicationRequestUuid: string,
|
|
239
|
+
fulfillerStatus: MedicationRequestFulfillerStatus,
|
|
240
|
+
) {
|
|
241
|
+
const url = `${fhirBaseUrl}/MedicationRequest/${medicationRequestUuid}`;
|
|
242
|
+
|
|
243
|
+
return openmrsFetch(url, {
|
|
244
|
+
method: 'PATCH',
|
|
245
|
+
headers: {
|
|
246
|
+
'Content-Type': JSON_MERGE_PATH_MIME_TYPE,
|
|
247
|
+
},
|
|
248
|
+
body: {
|
|
249
|
+
extension: [
|
|
250
|
+
{
|
|
251
|
+
url: OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS,
|
|
252
|
+
valueCode: fulfillerStatus,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { usePatientAge } from './patient.resources';
|
|
4
|
+
|
|
5
|
+
type PatientInfoCellProps = {
|
|
6
|
+
patient: {
|
|
7
|
+
name: string;
|
|
8
|
+
uuid: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const PatientInfoCell: React.FC<PatientInfoCellProps> = ({ patient: { name: display, uuid } }) => {
|
|
13
|
+
const { error, isLoading, age } = usePatientAge(uuid);
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const ageLabel = t('age', 'Age');
|
|
16
|
+
function concatAgePatientDisplay(input: string, age: number): string | null {
|
|
17
|
+
const attrIndex = input.lastIndexOf(')');
|
|
18
|
+
if (attrIndex !== -1) return input.slice(0, attrIndex) + `, ${ageLabel}: ${age}` + input.slice(attrIndex);
|
|
19
|
+
else return `${input} ${ageLabel}: ${age}`;
|
|
20
|
+
}
|
|
21
|
+
if (isLoading || error) return <>{display}</>;
|
|
22
|
+
const displayWithAge = concatAgePatientDisplay(display, age);
|
|
23
|
+
return <>{displayWithAge}</>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default PatientInfoCell;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
|
|
4
|
+
export const usePatientAge = (patienUuid: string) => {
|
|
5
|
+
const customRep = 'custom:(age)';
|
|
6
|
+
const url = `${restBaseUrl}/person/${patienUuid}?v=${customRep}`;
|
|
7
|
+
const { data, error, isLoading, mutate } = useSWR<FetchResponse<{ age: number }>>(url, openmrsFetch);
|
|
8
|
+
return {
|
|
9
|
+
age: data?.data?.age,
|
|
10
|
+
error,
|
|
11
|
+
isLoading,
|
|
12
|
+
mutate,
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Calendar, Location } from '@carbon/react/icons';
|
|
4
|
+
import { useConfig, useSession, formatDate } from '@openmrs/esm-framework';
|
|
5
|
+
import PharmacyIllustration from './pharmacy-illustration.component';
|
|
6
|
+
import { type PharmacyConfig } from '../config-schema';
|
|
7
|
+
import styles from './pharmacy-header.scss';
|
|
8
|
+
|
|
9
|
+
export const PharmacyHeader: React.FC = () => {
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
const userSession = useSession();
|
|
12
|
+
const config = useConfig<PharmacyConfig>();
|
|
13
|
+
const userLocation = userSession?.sessionLocation?.display;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className={styles.header}>
|
|
17
|
+
<div className={styles.leftJustifiedItems}>
|
|
18
|
+
<PharmacyIllustration />
|
|
19
|
+
<div className={styles.pageLabels}>
|
|
20
|
+
<p>{t('appName', config.appName)}</p>
|
|
21
|
+
<p className={styles.pageName}>{t('home', 'Home')}</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div className={styles.rightJustifiedItems}>
|
|
25
|
+
<div className={styles.dateAndLocation}>
|
|
26
|
+
<Location size={16} />
|
|
27
|
+
<span className={styles.value}>{userLocation}</span>
|
|
28
|
+
<span className={styles.middot}>·</span>
|
|
29
|
+
<Calendar size={16} />
|
|
30
|
+
<span className={styles.value}>{formatDate(new Date(), { noToday: true })}</span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
|
+
|
|
5
|
+
.header {
|
|
6
|
+
@include type.type-style('body-compact-02');
|
|
7
|
+
color: $text-02;
|
|
8
|
+
height: layout.$spacing-12;
|
|
9
|
+
background-color: $ui-02;
|
|
10
|
+
display: flex;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.leftJustifiedItems {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
align-items: center;
|
|
18
|
+
margin-left: layout.$spacing-05;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.rightJustifiedItems {
|
|
22
|
+
@include type.type-style('body-compact-02');
|
|
23
|
+
color: $text-02;
|
|
24
|
+
padding-top: layout.$spacing-05;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.pageName {
|
|
28
|
+
@include type.type-style('heading-04');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.pageLabels {
|
|
32
|
+
margin-left: layout.$spacing-05;
|
|
33
|
+
p:first-of-type {
|
|
34
|
+
margin-bottom: layout.$spacing-02;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.dateAndLocation {
|
|
39
|
+
display: flex;
|
|
40
|
+
justify-content: flex-end;
|
|
41
|
+
align-items: center;
|
|
42
|
+
margin-right: layout.$spacing-05;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.value {
|
|
46
|
+
margin-left: layout.$spacing-02;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.middot {
|
|
50
|
+
margin: 0 layout.$spacing-03;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.view {
|
|
54
|
+
@include type.type-style('label-01');
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const PharmacyIllustration: React.FC = () => {
|
|
4
|
+
return (
|
|
5
|
+
<svg width="72" height="72" viewBox="0 0 62 58" xmlns="http://www.w3.org/2000/svg">
|
|
6
|
+
<defs>
|
|
7
|
+
<path id="whv9q6f4ta" d="M0 0h62v30.508H0z" />
|
|
8
|
+
<path id="hp71ojgcnc" d="M0 0h24.603v26.571H0z" />
|
|
9
|
+
</defs>
|
|
10
|
+
<g fill="none">
|
|
11
|
+
<g opacity=".193" transform="matrix(-1 0 0 1 62 26.571)">
|
|
12
|
+
<path
|
|
13
|
+
d="M62 0c0 16.85-13.916 30.508-31.037 30.508S0 16.849 0 0h62z"
|
|
14
|
+
fill="var(--brand-03)"
|
|
15
|
+
mask="url(#rt0c8mo6eb)"
|
|
16
|
+
/>
|
|
17
|
+
</g>
|
|
18
|
+
<g opacity=".396" transform="matrix(-1 0 0 1 60.032 0)">
|
|
19
|
+
<path
|
|
20
|
+
d="m24.603 26.571-13.35-23.51C9.592.141 5.884-.869 3.011.817.138 2.503-.855 6.272.804 9.193l9.874 17.378"
|
|
21
|
+
fill="var(--brand-03)"
|
|
22
|
+
mask="url(#hgianthqwd)"
|
|
23
|
+
/>
|
|
24
|
+
</g>
|
|
25
|
+
</g>
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default PharmacyIllustration;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, Search, TabPanel } from '@carbon/react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { PatientSearchPictogram } from '@openmrs/esm-framework';
|
|
5
|
+
import PrescriptionsTable from './prescriptions-table.component';
|
|
6
|
+
import styles from './patient-search-tab-panel.scss';
|
|
7
|
+
|
|
8
|
+
const PatientSearchTabPanel: React.FC = () => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
11
|
+
const [submittedSearchTerm, setSubmittedSearchTerm] = useState('');
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<TabPanel>
|
|
15
|
+
<div className={styles.searchTabPanel}>
|
|
16
|
+
<form
|
|
17
|
+
className={styles.searchBar}
|
|
18
|
+
onSubmit={(e) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
setSubmittedSearchTerm(searchTerm);
|
|
21
|
+
}}>
|
|
22
|
+
<Search
|
|
23
|
+
closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
|
|
24
|
+
defaultValue={searchTerm}
|
|
25
|
+
placeholder={t('searchForPatient', 'Search for a patient by name or identifier number')}
|
|
26
|
+
labelText={t('searchForPatient', 'Search for a patient by name or identifier number')}
|
|
27
|
+
onChange={(e) => {
|
|
28
|
+
setSearchTerm(e.target.value);
|
|
29
|
+
}}
|
|
30
|
+
onClear={() => setSubmittedSearchTerm('')}
|
|
31
|
+
size="lg"
|
|
32
|
+
/>
|
|
33
|
+
<Button kind="secondary" type="submit">
|
|
34
|
+
{t('search', 'Search')}
|
|
35
|
+
</Button>
|
|
36
|
+
</form>
|
|
37
|
+
{submittedSearchTerm ? (
|
|
38
|
+
<PrescriptionsTable
|
|
39
|
+
loadData={true}
|
|
40
|
+
status={'ACTIVE'}
|
|
41
|
+
debouncedSearchTerm={submittedSearchTerm}
|
|
42
|
+
locations={[]}
|
|
43
|
+
/>
|
|
44
|
+
) : (
|
|
45
|
+
<div className={styles.searchForPatientPlaceholder}>
|
|
46
|
+
<div>
|
|
47
|
+
<PatientSearchPictogram />
|
|
48
|
+
<h5>{t('searchForPatientHeader', 'Search for a patient')}</h5>
|
|
49
|
+
<div>{t('searchForPatient', 'Search for a patient by name or identifier number')}</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
</TabPanel>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default PatientSearchTabPanel;
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Layer } from '@carbon/react';
|
|
3
|
+
import PrescriptionPrintAction from '../print-prescription/prescription-print-action.component';
|
|
4
|
+
import styles from './prescription-actions.scss';
|
|
5
|
+
|
|
6
|
+
type PrescriptionsActionsFooterProps = {
|
|
7
|
+
encounterUuid: string;
|
|
8
|
+
patientUuid: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const PrescriptionsActionsFooter: React.FC<PrescriptionsActionsFooterProps> = ({ encounterUuid, patientUuid }) => {
|
|
12
|
+
return (
|
|
13
|
+
<Layer className={styles.actionsContainer}>
|
|
14
|
+
<div className={styles.actionCluster}>
|
|
15
|
+
{/* Left buttons */}
|
|
16
|
+
<PrescriptionPrintAction encounterUuid={encounterUuid} patientUuid={patientUuid} />
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div className={styles.actionCluster}>{/* Right buttons */}</div>
|
|
20
|
+
</Layer>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default PrescriptionsActionsFooter;
|