@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.
Files changed (277) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc +80 -0
  4. package/.husky/pre-commit +7 -0
  5. package/.husky/pre-push +6 -0
  6. package/.prettierignore +12 -0
  7. package/.tx/config +11 -0
  8. package/.yarn/versions/1c40b9b6.yml +0 -0
  9. package/.yarn/versions/ff162597.yml +0 -0
  10. package/LICENSE +401 -0
  11. package/README.md +124 -0
  12. package/__mocks__/react-i18next.js +51 -0
  13. package/dist/1043.js +1 -0
  14. package/dist/1043.js.map +1 -0
  15. package/dist/1119.js +1 -0
  16. package/dist/1197.js +1 -0
  17. package/dist/2146.js +1 -0
  18. package/dist/2177.js +2 -0
  19. package/dist/2177.js.LICENSE.txt +9 -0
  20. package/dist/2177.js.map +1 -0
  21. package/dist/2690.js +1 -0
  22. package/dist/2890.js +2 -0
  23. package/dist/2890.js.LICENSE.txt +14 -0
  24. package/dist/2890.js.map +1 -0
  25. package/dist/2898.js +1 -0
  26. package/dist/2898.js.map +1 -0
  27. package/dist/3041.js +1 -0
  28. package/dist/3041.js.map +1 -0
  29. package/dist/3099.js +1 -0
  30. package/dist/3184.js +2 -0
  31. package/dist/3184.js.LICENSE.txt +14 -0
  32. package/dist/3184.js.map +1 -0
  33. package/dist/3568.js +1 -0
  34. package/dist/3568.js.map +1 -0
  35. package/dist/3584.js +1 -0
  36. package/dist/4055.js +1 -0
  37. package/dist/4099.js +1 -0
  38. package/dist/4099.js.map +1 -0
  39. package/dist/4132.js +1 -0
  40. package/dist/4225.js +1 -0
  41. package/dist/4225.js.map +1 -0
  42. package/dist/4300.js +1 -0
  43. package/dist/4335.js +1 -0
  44. package/dist/4353.js +1 -0
  45. package/dist/4353.js.map +1 -0
  46. package/dist/439.js +1 -0
  47. package/dist/4618.js +1 -0
  48. package/dist/4652.js +1 -0
  49. package/dist/4944.js +1 -0
  50. package/dist/5173.js +1 -0
  51. package/dist/5241.js +1 -0
  52. package/dist/5422.js +1 -0
  53. package/dist/5422.js.map +1 -0
  54. package/dist/5442.js +1 -0
  55. package/dist/5661.js +1 -0
  56. package/dist/5897.js +1 -0
  57. package/dist/5897.js.map +1 -0
  58. package/dist/6022.js +1 -0
  59. package/dist/609.js +1 -0
  60. package/dist/609.js.map +1 -0
  61. package/dist/6468.js +1 -0
  62. package/dist/6540.js +2 -0
  63. package/dist/6540.js.LICENSE.txt +9 -0
  64. package/dist/6540.js.map +1 -0
  65. package/dist/6589.js +1 -0
  66. package/dist/6606.js +1 -0
  67. package/dist/6606.js.map +1 -0
  68. package/dist/6679.js +1 -0
  69. package/dist/6825.js +1 -0
  70. package/dist/6825.js.map +1 -0
  71. package/dist/6840.js +1 -0
  72. package/dist/6841.js +1 -0
  73. package/dist/6841.js.map +1 -0
  74. package/dist/6859.js +1 -0
  75. package/dist/7097.js +1 -0
  76. package/dist/7159.js +1 -0
  77. package/dist/723.js +1 -0
  78. package/dist/7240.js +1 -0
  79. package/dist/7240.js.map +1 -0
  80. package/dist/7255.js +1 -0
  81. package/dist/7255.js.map +1 -0
  82. package/dist/7617.js +1 -0
  83. package/dist/795.js +1 -0
  84. package/dist/8163.js +1 -0
  85. package/dist/8349.js +1 -0
  86. package/dist/8371.js +1 -0
  87. package/dist/8569.js +2 -0
  88. package/dist/8569.js.LICENSE.txt +41 -0
  89. package/dist/8569.js.map +1 -0
  90. package/dist/8600.js +1 -0
  91. package/dist/8600.js.map +1 -0
  92. package/dist/8618.js +1 -0
  93. package/dist/8885.js +1 -0
  94. package/dist/8885.js.map +1 -0
  95. package/dist/890.js +1 -0
  96. package/dist/9214.js +1 -0
  97. package/dist/9538.js +1 -0
  98. package/dist/9569.js +1 -0
  99. package/dist/961.js +2 -0
  100. package/dist/961.js.LICENSE.txt +19 -0
  101. package/dist/961.js.map +1 -0
  102. package/dist/963.js +1 -0
  103. package/dist/963.js.map +1 -0
  104. package/dist/986.js +1 -0
  105. package/dist/9879.js +1 -0
  106. package/dist/9895.js +1 -0
  107. package/dist/9900.js +1 -0
  108. package/dist/9913.js +1 -0
  109. package/dist/main.js +2 -0
  110. package/dist/main.js.LICENSE.txt +51 -0
  111. package/dist/main.js.map +1 -0
  112. package/dist/openmrs-esm-dispensing-app.js +1 -0
  113. package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +1645 -0
  114. package/dist/openmrs-esm-dispensing-app.js.map +1 -0
  115. package/dist/routes.json +1 -0
  116. package/e2e/README.md +119 -0
  117. package/e2e/commands/drug-order-operations.ts +43 -0
  118. package/e2e/commands/encounter-operations.ts +60 -0
  119. package/e2e/commands/index.ts +5 -0
  120. package/e2e/commands/patient-operations.ts +109 -0
  121. package/e2e/commands/provider-operations.ts +9 -0
  122. package/e2e/commands/types/index.ts +157 -0
  123. package/e2e/commands/visit-operations.ts +38 -0
  124. package/e2e/core/global-setup.ts +32 -0
  125. package/e2e/core/index.ts +1 -0
  126. package/e2e/core/test.ts +31 -0
  127. package/e2e/fixtures/api.ts +27 -0
  128. package/e2e/fixtures/index.ts +1 -0
  129. package/e2e/pages/dispensing-page.ts +9 -0
  130. package/e2e/pages/index.ts +1 -0
  131. package/e2e/specs/active-prescriptions.spec.ts +72 -0
  132. package/e2e/specs/close-prescription.spec.ts +71 -0
  133. package/e2e/specs/dispense-medication.spec.ts +71 -0
  134. package/e2e/specs/pause-prescription.spec.ts +72 -0
  135. package/e2e/support/github/Dockerfile +34 -0
  136. package/e2e/support/github/docker-compose.yml +24 -0
  137. package/e2e/support/github/run-e2e-docker-env.sh +42 -0
  138. package/e2e/types/index.ts +157 -0
  139. package/example.env +7 -0
  140. package/jest.config.js +24 -0
  141. package/package.json +110 -0
  142. package/playwright.config.ts +36 -0
  143. package/prettier.config.js +8 -0
  144. package/src/components/action-buttons.component.tsx +87 -0
  145. package/src/components/action-buttons.scss +16 -0
  146. package/src/components/action-buttons.test.tsx +217 -0
  147. package/src/components/medication-card.component.tsx +37 -0
  148. package/src/components/medication-card.scss +20 -0
  149. package/src/components/medication-card.test.tsx +36 -0
  150. package/src/components/medication-dispense-review.scss +108 -0
  151. package/src/components/medication-event.component.tsx +96 -0
  152. package/src/components/medication-event.scss +44 -0
  153. package/src/components/medication-event.test.tsx +212 -0
  154. package/src/components/prescription-actions/close-action-button.component.tsx +50 -0
  155. package/src/components/prescription-actions/dispense-action-button.component.tsx +57 -0
  156. package/src/components/prescription-actions/pause-action-button.component.tsx +49 -0
  157. package/src/conditions/conditions.component.tsx +118 -0
  158. package/src/conditions/conditions.resource.ts +100 -0
  159. package/src/conditions/conditions.scss +26 -0
  160. package/src/conditions/conditions.test.tsx +200 -0
  161. package/src/config-schema.ts +192 -0
  162. package/src/constants.ts +22 -0
  163. package/src/dashboard/dispensing-dashboard-link.component.tsx +36 -0
  164. package/src/dashboard/dispensing-dashboard.component.tsx +36 -0
  165. package/src/declarations.d.ts +2 -0
  166. package/src/diagnoses/diagnoses.component.tsx +111 -0
  167. package/src/diagnoses/diagnoses.resource.ts +30 -0
  168. package/src/diagnoses/diagnoses.scss +31 -0
  169. package/src/dispensing-link.component.tsx +9 -0
  170. package/src/dispensing-tiles/dispensing-tile.component.tsx +42 -0
  171. package/src/dispensing-tiles/dispensing-tile.scss +43 -0
  172. package/src/dispensing-tiles/dispensing-tiles.component.tsx +39 -0
  173. package/src/dispensing-tiles/dispensing-tiles.resource.tsx +30 -0
  174. package/src/dispensing-tiles/dispensing-tiles.scss +11 -0
  175. package/src/dispensing.component.tsx +31 -0
  176. package/src/dispensing.scss +5 -0
  177. package/src/dispensing.test.tsx +9 -0
  178. package/src/fill-prescription/fill-prescription-button.component.tsx +103 -0
  179. package/src/fill-prescription/fill-prescription-button.scss +8 -0
  180. package/src/fill-prescription/on-prescription-filled.modal.tsx +140 -0
  181. package/src/fill-prescription/on-prescription-filled.scss +7 -0
  182. package/src/forms/close-dispense-form.workspace.tsx +194 -0
  183. package/src/forms/dispense-form.workspace.test.tsx +334 -0
  184. package/src/forms/dispense-form.workspace.tsx +324 -0
  185. package/src/forms/forms.scss +152 -0
  186. package/src/forms/medication-dispense-review.component.tsx +649 -0
  187. package/src/forms/medication-dispense-review.test.tsx +158 -0
  188. package/src/forms/pause-dispense-form.workspace.tsx +196 -0
  189. package/src/forms/stock-dispense/stock-dispense.component.tsx +126 -0
  190. package/src/forms/stock-dispense/stock.resource.tsx +67 -0
  191. package/src/history/delete-confirm.modal.tsx +35 -0
  192. package/src/history/history-and-comments.component.tsx +338 -0
  193. package/src/history/history-and-comments.scss +54 -0
  194. package/src/index.ts +57 -0
  195. package/src/location/location.resource.test.tsx +108 -0
  196. package/src/location/location.resource.tsx +32 -0
  197. package/src/medication/medication.resource.test.tsx +156 -0
  198. package/src/medication/medication.resource.tsx +45 -0
  199. package/src/medication-dispense/medication-dispense.resource.test.tsx +243 -0
  200. package/src/medication-dispense/medication-dispense.resource.tsx +178 -0
  201. package/src/medication-request/medication-request.resource.test.tsx +1333 -0
  202. package/src/medication-request/medication-request.resource.tsx +257 -0
  203. package/src/patient/patient-info-cell.component.tsx +26 -0
  204. package/src/patient/patient.resources.ts +14 -0
  205. package/src/pharmacy-header/pharmacy-header.component.tsx +35 -0
  206. package/src/pharmacy-header/pharmacy-header.scss +55 -0
  207. package/src/pharmacy-header/pharmacy-illustration.component.tsx +30 -0
  208. package/src/prescriptions/patient-search-tab-panel.component.tsx +58 -0
  209. package/src/prescriptions/patient-search-tab-panel.scss +26 -0
  210. package/src/prescriptions/prescription-actions.component.tsx +24 -0
  211. package/src/prescriptions/prescription-actions.scss +14 -0
  212. package/src/prescriptions/prescription-details.component.tsx +152 -0
  213. package/src/prescriptions/prescription-details.scss +87 -0
  214. package/src/prescriptions/prescription-details.test.tsx +267 -0
  215. package/src/prescriptions/prescription-expanded.component.tsx +58 -0
  216. package/src/prescriptions/prescription-expanded.scss +56 -0
  217. package/src/prescriptions/prescription-tab-lists.component.tsx +70 -0
  218. package/src/prescriptions/prescription-tab-panel.component.tsx +83 -0
  219. package/src/prescriptions/prescriptions-table.component.tsx +189 -0
  220. package/src/prescriptions/prescriptions.scss +152 -0
  221. package/src/print-prescription/prescription-print-action.component.tsx +30 -0
  222. package/src/print-prescription/prescription-print-preview.modal.tsx +92 -0
  223. package/src/print-prescription/prescription-printout.component.tsx +154 -0
  224. package/src/print-prescription/print-prescription.scss +75 -0
  225. package/src/print-prescription/printable-prescriptions.component.tsx +57 -0
  226. package/src/routes.json +137 -0
  227. package/src/types.ts +530 -0
  228. package/src/utils.test.ts +2947 -0
  229. package/src/utils.ts +637 -0
  230. package/tools/i18next-parser.config.js +89 -0
  231. package/tools/setup-tests.ts +8 -0
  232. package/tools/update-openmrs-deps.mjs +42 -0
  233. package/translations/am.json +133 -0
  234. package/translations/ar.json +133 -0
  235. package/translations/ar_SY.json +133 -0
  236. package/translations/bn.json +133 -0
  237. package/translations/cs.json +133 -0
  238. package/translations/de.json +133 -0
  239. package/translations/en.json +133 -0
  240. package/translations/en_US.json +133 -0
  241. package/translations/es.json +133 -0
  242. package/translations/es_MX.json +133 -0
  243. package/translations/fr.json +133 -0
  244. package/translations/he.json +133 -0
  245. package/translations/hi.json +133 -0
  246. package/translations/hi_IN.json +133 -0
  247. package/translations/id.json +133 -0
  248. package/translations/it.json +133 -0
  249. package/translations/ka.json +133 -0
  250. package/translations/km.json +133 -0
  251. package/translations/ku.json +133 -0
  252. package/translations/ky.json +133 -0
  253. package/translations/lg.json +133 -0
  254. package/translations/ne.json +133 -0
  255. package/translations/pl.json +133 -0
  256. package/translations/pt.json +133 -0
  257. package/translations/pt_BR.json +133 -0
  258. package/translations/qu.json +133 -0
  259. package/translations/ro_RO.json +133 -0
  260. package/translations/ru_RU.json +133 -0
  261. package/translations/si.json +133 -0
  262. package/translations/sq.json +133 -0
  263. package/translations/sw.json +133 -0
  264. package/translations/sw_KE.json +133 -0
  265. package/translations/tr.json +133 -0
  266. package/translations/tr_TR.json +133 -0
  267. package/translations/uk.json +133 -0
  268. package/translations/uz.json +133 -0
  269. package/translations/uz@Latn.json +133 -0
  270. package/translations/uz_UZ.json +133 -0
  271. package/translations/vi.json +133 -0
  272. package/translations/zh.json +133 -0
  273. package/translations/zh_CN.json +133 -0
  274. package/translations/zh_TW.json +133 -0
  275. package/tsconfig.json +23 -0
  276. package/turbo.json +41 -0
  277. package/webpack.config.js +1 -0
@@ -0,0 +1,111 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ DataTable,
5
+ DataTableSkeleton,
6
+ Layer,
7
+ Pagination,
8
+ Table,
9
+ TableBody,
10
+ TableCell,
11
+ TableHead,
12
+ TableHeader,
13
+ TableRow,
14
+ } from '@carbon/react';
15
+ import { CardHeader, EmptyCard, ErrorState, usePagination } from '@openmrs/esm-framework';
16
+ import { usePatientDiagnosis } from './diagnoses.resource';
17
+ import styles from './diagnoses.scss';
18
+
19
+ type PatientDiagnosesProps = {
20
+ patientUuid: string;
21
+ encounterUuid: string;
22
+ };
23
+
24
+ const PatientDiagnoses: React.FC<PatientDiagnosesProps> = ({ encounterUuid, patientUuid }) => {
25
+ const { diagnoses, isLoading, error } = usePatientDiagnosis(encounterUuid);
26
+ const [pageSize, setPageSize] = useState(3);
27
+ const pageSizesOptions = useMemo(() => [3, 5, 10, 20, 50, 100], []);
28
+ const { results, totalPages, currentPage, goTo } = usePagination(diagnoses, pageSize);
29
+ const { t } = useTranslation();
30
+ const title = t('diagnoses', 'Diagnoses');
31
+ const headers = useMemo(
32
+ () => [
33
+ { header: t('diagnosis', 'Diagnosis'), key: 'text' },
34
+ { header: t('status', 'Status'), key: 'certainty' },
35
+ ],
36
+ [t],
37
+ );
38
+
39
+ const getColumnClass = (key: string) => {
40
+ switch (key) {
41
+ case 'text':
42
+ return styles.diagnosisColumn;
43
+ case 'certainty':
44
+ return styles.statusColumn;
45
+ default:
46
+ return '';
47
+ }
48
+ };
49
+
50
+ if (isLoading) {
51
+ return <DataTableSkeleton />;
52
+ }
53
+
54
+ if (error) {
55
+ return <ErrorState headerTitle={title} error={error} />;
56
+ }
57
+
58
+ if (!diagnoses?.length) {
59
+ return (
60
+ <Layer className={styles.diagnosesContainer}>
61
+ <EmptyCard headerTitle={title} displayText={t('diagnosesEmpty', 'diagnoses')} />
62
+ </Layer>
63
+ );
64
+ }
65
+
66
+ return (
67
+ <Layer className={styles.diagnosesContainer}>
68
+ <CardHeader title={title}>
69
+ <React.Fragment />
70
+ </CardHeader>
71
+ <DataTable useZebraStyles rows={results} headers={headers}>
72
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
73
+ <Table {...getTableProps()} className={styles.table}>
74
+ <TableHead>
75
+ <TableRow>
76
+ {headers.map((header) => (
77
+ <TableHeader {...getHeaderProps({ header })} key={header.key} className={getColumnClass(header.key)}>
78
+ {header.header}
79
+ </TableHeader>
80
+ ))}
81
+ </TableRow>
82
+ </TableHead>
83
+ <TableBody>
84
+ {rows.map((row) => (
85
+ <TableRow {...getRowProps({ row })} key={row.id}>
86
+ {row.cells.map((cell) => (
87
+ <TableCell key={cell.id} className={getColumnClass(cell.info.header)}>
88
+ {cell.value}
89
+ </TableCell>
90
+ ))}
91
+ </TableRow>
92
+ ))}
93
+ </TableBody>
94
+ </Table>
95
+ )}
96
+ </DataTable>
97
+ <Pagination
98
+ page={currentPage}
99
+ pageSize={pageSize}
100
+ pageSizes={pageSizesOptions}
101
+ totalItems={diagnoses.length}
102
+ onChange={({ page, pageSize }) => {
103
+ goTo(page);
104
+ setPageSize(pageSize);
105
+ }}
106
+ />
107
+ </Layer>
108
+ );
109
+ };
110
+
111
+ export default PatientDiagnoses;
@@ -0,0 +1,30 @@
1
+ import { useMemo } from 'react';
2
+ import useSWR from 'swr';
3
+ import { type FetchResponse, openmrsFetch, restBaseUrl, type Visit } from '@openmrs/esm-framework';
4
+
5
+ export const usePatientDiagnosis = (encounterUuid: string) => {
6
+ const customRepresentation =
7
+ 'custom:(uuid,display,visit:(uuid,encounters:(uuid,diagnoses:(uuid,display,certainty,diagnosis:(coded:(uuid,display))))))';
8
+ const url = `${restBaseUrl}/encounter/${encounterUuid}?v=${customRepresentation}`;
9
+
10
+ const { data, error, isLoading } = useSWR<FetchResponse<{ visit: Visit }>>(url, openmrsFetch);
11
+
12
+ const diagnoses = useMemo(() => {
13
+ return (
14
+ data?.data?.visit?.encounters?.flatMap(
15
+ (encounter) =>
16
+ encounter.diagnoses.map((diagnosis) => ({
17
+ id: diagnosis.diagnosis.coded.uuid,
18
+ text: diagnosis.display,
19
+ certainty: diagnosis.certainty,
20
+ })) || [],
21
+ ) || []
22
+ );
23
+ }, [data]);
24
+
25
+ return {
26
+ error,
27
+ isLoading,
28
+ diagnoses: (diagnoses ?? []) as Array<{ id: string; text: string; certainty: string }>,
29
+ };
30
+ };
@@ -0,0 +1,31 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/colors';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .diagnosesContainer {
6
+ background-color: colors.$white-0;
7
+ margin-bottom: layout.$spacing-05;
8
+
9
+ :global(.cds--tile) {
10
+ min-height: unset !important;
11
+ padding: layout.$spacing-05 !important;
12
+ }
13
+ }
14
+
15
+ .table {
16
+ table-layout: fixed;
17
+ width: 100%;
18
+
19
+ td {
20
+ padding-left: 2rem !important;
21
+ }
22
+ }
23
+
24
+ .diagnosisColumn {
25
+ width: 70%;
26
+ }
27
+
28
+ .statusColumn {
29
+ width: 30%;
30
+ text-align: left;
31
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { ConfigurableLink } from '@openmrs/esm-framework';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { spaBasePath } from './constants';
5
+
6
+ export default function DispensingLink() {
7
+ const { t } = useTranslation();
8
+ return <ConfigurableLink to={spaBasePath}>{t('dispensing', 'Dispensing')}</ConfigurableLink>;
9
+ }
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Tile, Button } from '@carbon/react';
4
+ import { ArrowRight } from '@carbon/react/icons';
5
+ import { ResponsiveWrapper } from '@openmrs/esm-framework';
6
+ import styles from './dispensing-tile.scss';
7
+
8
+ interface DispensingTileProps {
9
+ label: string;
10
+ value: number;
11
+ headerLabel: string;
12
+ children?: React.ReactNode;
13
+ }
14
+
15
+ const DispensingTile: React.FC<DispensingTileProps> = ({ label, value, headerLabel, children }) => {
16
+ const { t } = useTranslation();
17
+
18
+ return (
19
+ <ResponsiveWrapper>
20
+ <Tile className={styles.tileContainer}>
21
+ <div className={styles.tileHeader}>
22
+ <div className={styles.headerLabelContainer}>
23
+ <label className={styles.headerLabel}>{headerLabel}</label>
24
+ {children}
25
+ </div>
26
+ <Button
27
+ kind="ghost"
28
+ renderIcon={(props) => <ArrowRight size={16} className={styles.arrowIcon} />}
29
+ iconDescription={t('view', 'View')}>
30
+ {t('view', 'View')}
31
+ </Button>
32
+ </div>
33
+ <div>
34
+ <label className={styles.totalsLabel}>{label}</label>
35
+ <p className={styles.totalsValue}>{value}</p>
36
+ </div>
37
+ </Tile>
38
+ </ResponsiveWrapper>
39
+ );
40
+ };
41
+
42
+ export default DispensingTile;
@@ -0,0 +1,43 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .tileContainer {
6
+ border: 1px solid $ui-03;
7
+ flex-grow: 1;
8
+ height: 7.875rem;
9
+ padding: 0 layout.$spacing-05;
10
+ margin: layout.$spacing-03 layout.$spacing-03;
11
+ }
12
+
13
+ .tileHeader {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ align-items: center;
17
+ margin-bottom: layout.$spacing-03;
18
+ }
19
+
20
+ .headerLabel {
21
+ @include type.type-style('heading-01');
22
+ color: $text-02;
23
+ }
24
+
25
+ .totalsLabel {
26
+ @include type.type-style('label-01');
27
+ color: $text-02;
28
+ }
29
+
30
+ .totalsValue {
31
+ @include type.type-style('heading-04');
32
+ color: $ui-05;
33
+ }
34
+
35
+ .headerLabelContainer {
36
+ display: flex;
37
+ align-items: center;
38
+ height: layout.$spacing-07;
39
+ }
40
+
41
+ .arrowIcon {
42
+ fill: var(--cds-link-primary, #0f62fe) !important;
43
+ }
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { DataTableSkeleton } from '@carbon/react';
4
+ import { useMetrics } from './dispensing-tiles.resource';
5
+ import DispensingTile from './dispensing-tile.component';
6
+ import styles from './dispensing-tiles.scss';
7
+
8
+ const DispensingTiles: React.FC = () => {
9
+ const { t } = useTranslation();
10
+ const { metrics, error, isLoading } = useMetrics();
11
+
12
+ if (isLoading) {
13
+ return <DataTableSkeleton role="progressbar" />;
14
+ }
15
+
16
+ return (
17
+ <>
18
+ <div className={styles.cardContainer}>
19
+ <DispensingTile
20
+ label={t('orders', 'Orders')}
21
+ value={metrics.orders}
22
+ headerLabel={t('prescriptionsToFillToday', 'Prescriptions to fill today')}
23
+ />
24
+ <DispensingTile
25
+ label={t('today', 'Today')}
26
+ value={metrics.orders_for_home_delivery}
27
+ headerLabel={t('ordersForHomeDelivery', 'Orders for home delivery')}
28
+ />
29
+ <DispensingTile
30
+ label={t('last14Days', 'Last 14 days')}
31
+ value={metrics.missed_collections}
32
+ headerLabel={t('missedCollections', 'Missed collections')}
33
+ />
34
+ </div>
35
+ </>
36
+ );
37
+ };
38
+
39
+ export default DispensingTiles;
@@ -0,0 +1,30 @@
1
+ import useSWR from 'swr';
2
+ import useSWRImmutable from 'swr/immutable';
3
+ import { type FetchResponse, openmrsFetch } from '@openmrs/esm-framework';
4
+
5
+ // NOT CURRENTLY USED
6
+
7
+ export function useMetrics() {
8
+ const metrics = {
9
+ orders: 43,
10
+ orders_for_home_delivery: 4,
11
+ missed_collections: 12,
12
+ };
13
+ const { data, error } = useSWR<{ data: { results: {} } }, Error>(`/ws/rest/v1/queue?`, openmrsFetch);
14
+
15
+ return {
16
+ metrics: metrics,
17
+ error,
18
+ isLoading: !data && !error,
19
+ };
20
+ }
21
+
22
+ export function useServices() {
23
+ const serviceConceptSetUuid = '330c0ec6-0ac7-4b86-9c70-29d76f0ae20a';
24
+ const apiUrl = `/ws/rest/v1/concept/${serviceConceptSetUuid}`;
25
+ const { data } = useSWRImmutable<FetchResponse>(apiUrl, openmrsFetch);
26
+
27
+ return {
28
+ services: data ? data?.data?.setMembers?.map((setMember) => setMember?.display) : [],
29
+ };
30
+ }
@@ -0,0 +1,11 @@
1
+ @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .cardContainer {
5
+ background-color: $ui-02;
6
+ display: flex;
7
+ justify-content: space-between;
8
+ padding: 0 layout.$spacing-05 layout.$spacing-10 layout.$spacing-03;
9
+ flex-flow: row wrap;
10
+ margin-top: -layout.$spacing-03;
11
+ }
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import {
4
+ ExtensionSlot,
5
+ isDesktop,
6
+ useConfig,
7
+ useLayoutType,
8
+ useLeftNav,
9
+ WorkspaceContainer,
10
+ } from '@openmrs/esm-framework';
11
+ import { type PharmacyConfig } from './config-schema';
12
+ import styles from './dispensing.scss';
13
+
14
+ export default function Dispensing() {
15
+ const { leftNavMode } = useConfig<PharmacyConfig>();
16
+ const layout = useLayoutType();
17
+
18
+ const basePath = window.spaBase + '/dispensing';
19
+ useLeftNav({ name: 'homepage-dashboard-slot', basePath, mode: leftNavMode });
20
+
21
+ return (
22
+ <div
23
+ className={classNames([
24
+ isDesktop(layout) ? styles.desktopContainer : '',
25
+ leftNavMode === 'normal' ? styles.hasLeftNav : '',
26
+ ])}>
27
+ <ExtensionSlot name="dispensing-dashboard-slot" />
28
+ <WorkspaceContainer key="dispensing" contextKey="dispensing" />
29
+ </div>
30
+ );
31
+ }
@@ -0,0 +1,5 @@
1
+ .dispensing {
2
+ &.hasLeftNav {
3
+ margin-left: 16rem;
4
+ }
5
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import Dispensing from './dispensing.component';
4
+
5
+ describe('<div/>', () => {
6
+ test('renders dispening without error', () => {
7
+ render(<Dispensing />);
8
+ });
9
+ });
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+ import { Button } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import {
5
+ AddIcon,
6
+ launchWorkspace2,
7
+ openmrsFetch,
8
+ restBaseUrl,
9
+ showModal,
10
+ showSnackbar,
11
+ useLayoutType,
12
+ type FetchResponse,
13
+ type Order,
14
+ type Visit,
15
+ type Workspace2DefinitionProps,
16
+ } from '@openmrs/esm-framework';
17
+ import styles from './fill-prescription-button.scss';
18
+
19
+ const FillPrescriptionButton: React.FC<{}> = () => {
20
+ const isTablet = useLayoutType() === 'tablet';
21
+ const responsiveSize = isTablet ? 'lg' : 'md';
22
+ const { t } = useTranslation();
23
+
24
+ const launchSearchWorkspace = () => {
25
+ launchWorkspace2(
26
+ 'dispensing-patient-search-workspace',
27
+ {
28
+ workspaceTitle: t('fillPrescriptionForPatient', 'Fill prescription for patient'),
29
+ onPatientSelected(
30
+ patientUuid: string,
31
+ patient: fhir.Patient,
32
+ launchChildWorkspace: Workspace2DefinitionProps['launchChildWorkspace'],
33
+ closeWorkspace: Workspace2DefinitionProps['closeWorkspace'],
34
+ ) {
35
+ getActiveVisitsForPatient(patientUuid).then(async (response) => {
36
+ const activeVisit = response.data.results?.[0];
37
+ if (activeVisit) {
38
+ await closeWorkspace();
39
+ launchWorkspace2(
40
+ 'dispensing-order-basket-workspace',
41
+ {},
42
+ {
43
+ patientUuid: patientUuid,
44
+ patient: patient,
45
+ visitContext: activeVisit,
46
+ drugOrderWorkspaceName: 'dispensing-order-basket-add-drug-order-workspace',
47
+ onOrderBasketSubmitted: (encounterUuid: string, _: Array<Order>) => {
48
+ showModal('on-prescription-filled-modal', {
49
+ patient,
50
+ encounterUuid,
51
+ });
52
+ },
53
+ },
54
+ );
55
+ } else {
56
+ showSnackbar({
57
+ title: t('visitRequired', 'Visit required'),
58
+ subtitle: t(
59
+ 'visitRequiredForPatientToFillPrescription',
60
+ 'Visit required for patient to fill prescription',
61
+ ),
62
+ kind: 'error',
63
+ });
64
+ }
65
+ });
66
+ },
67
+ },
68
+ {
69
+ startVisitWorkspaceName: 'dispensing-start-visit-workspace',
70
+ },
71
+ );
72
+ };
73
+
74
+ return (
75
+ <div className={styles.buttonContainer}>
76
+ <Button
77
+ kind="primary"
78
+ renderIcon={(props) => <AddIcon size={16} {...props} />}
79
+ size={responsiveSize}
80
+ onClick={launchSearchWorkspace}>
81
+ {t('fillPrescription', 'Fill prescription')}
82
+ </Button>
83
+ </div>
84
+ );
85
+ };
86
+
87
+ function getActiveVisitsForPatient(
88
+ patientUuid: string,
89
+ abortController?: AbortController,
90
+ v?: string,
91
+ ): Promise<FetchResponse<{ results: Array<Visit> }>> {
92
+ const custom = v ?? `default`;
93
+
94
+ return openmrsFetch(`${restBaseUrl}/visit?patient=${patientUuid}&v=${custom}&includeInactive=false`, {
95
+ signal: abortController?.signal,
96
+ method: 'GET',
97
+ headers: {
98
+ 'Content-type': 'application/json',
99
+ },
100
+ });
101
+ }
102
+
103
+ export default FillPrescriptionButton;
@@ -0,0 +1,8 @@
1
+ @use '@carbon/layout';
2
+
3
+ .buttonContainer {
4
+ display: flex;
5
+ flex-direction: row-reverse;
6
+ background-color: #fff;
7
+ margin-inline-end: layout.$spacing-04;
8
+ }
@@ -0,0 +1,140 @@
1
+ import React from 'react';
2
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
3
+ import { Trans, useTranslation } from 'react-i18next';
4
+ import { getPatientName, showSnackbar, useConfig, useSession } from '@openmrs/esm-framework';
5
+ import {
6
+ updateMedicationRequestFulfillerStatus,
7
+ usePrescriptionDetails,
8
+ } from '../medication-request/medication-request.resource';
9
+ import {
10
+ getMedicationDisplay,
11
+ getMedicationReferenceOrCodeableConcept,
12
+ getUuidFromReference,
13
+ markEncounterAsStale,
14
+ revalidate,
15
+ } from '../utils';
16
+ import {
17
+ initiateMedicationDispenseBody,
18
+ saveMedicationDispense,
19
+ useProviders,
20
+ } from '../medication-dispense/medication-dispense.resource';
21
+ import { type PharmacyConfig } from '../config-schema';
22
+ import MedicationEvent from '../components/medication-event.component';
23
+ import { MedicationDispenseStatus, MedicationRequestFulfillerStatus } from '../types';
24
+ import styles from './on-prescription-filled.scss';
25
+
26
+ interface OnPrescriptionFilledModalProps {
27
+ patient: fhir.Patient;
28
+
29
+ /**
30
+ * The encounter with which the user just placed the fill prescription order.
31
+ */
32
+ encounterUuid: string;
33
+
34
+ /**
35
+ * closes the modal
36
+ */
37
+ close(): void;
38
+ }
39
+
40
+ /**
41
+ * This modal appears after the user submits the order basket opened via the
42
+ * "Fill Prescription" button in the dispensing app. It confirms whether the user
43
+ * would like to immediately mark the medication orders as dispensed.
44
+ */
45
+ const OnPrescriptionFilledModal: React.FC<OnPrescriptionFilledModalProps> = ({ patient, encounterUuid, close }) => {
46
+ const { dispenserProviderRoles } = useConfig<PharmacyConfig>();
47
+ const session = useSession();
48
+ const providers = useProviders(dispenserProviderRoles);
49
+ const { medicationRequestBundles } = usePrescriptionDetails(encounterUuid);
50
+ const { t } = useTranslation();
51
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
52
+
53
+ const onConfirm = async () => {
54
+ setIsSubmitting(true);
55
+ markEncounterAsStale(encounterUuid);
56
+ try {
57
+ for (const medicationRequestBundle of medicationRequestBundles) {
58
+ const medicationDispensePayload = initiateMedicationDispenseBody(
59
+ medicationRequestBundle.request,
60
+ session,
61
+ providers,
62
+ true,
63
+ );
64
+ const medicationDisplay = getMedicationDisplay(
65
+ getMedicationReferenceOrCodeableConcept(medicationRequestBundle.request),
66
+ );
67
+
68
+ await saveMedicationDispense(medicationDispensePayload, MedicationDispenseStatus.completed)
69
+ .then((response) => {
70
+ const hasNoRefills = medicationRequestBundle.request.dispenseRequest.numberOfRepeatsAllowed == 0;
71
+ if (response.ok && hasNoRefills) {
72
+ return updateMedicationRequestFulfillerStatus(
73
+ getUuidFromReference(
74
+ medicationDispensePayload.authorizingPrescription[0].reference, // assumes authorizing prescription exist
75
+ ),
76
+ MedicationRequestFulfillerStatus.completed,
77
+ ).then(() => response);
78
+ } else {
79
+ return response;
80
+ }
81
+ })
82
+ .then(() => {
83
+ showSnackbar({
84
+ title: t('stockDispensed', 'Stock dispensed'),
85
+ subtitle: medicationDisplay,
86
+ isLowContrast: false,
87
+ });
88
+ })
89
+ .catch((error) => {
90
+ showSnackbar({
91
+ title: t('errorDispensingMedication', 'Error dispensing medication'),
92
+ kind: 'error',
93
+ subtitle: t('errorDispensingMedicationMessage', '{{medication}}: {{error}}', {
94
+ medication: medicationDisplay,
95
+ error: error?.message,
96
+ }),
97
+ });
98
+ });
99
+ }
100
+
101
+ close();
102
+ } finally {
103
+ revalidate(encounterUuid);
104
+ setIsSubmitting(false);
105
+ }
106
+ };
107
+
108
+ const patientName = getPatientName(patient);
109
+
110
+ return (
111
+ <>
112
+ <ModalHeader>{t('dispenseAllPrescriptions', 'Dispense prescriptions')}</ModalHeader>
113
+ <ModalBody>
114
+ <p className={styles.modalDescription}>
115
+ <Trans i18nKey="dispenseAllPrescriptionsConfirmation">
116
+ Would you like to mark prescriptions ordered for <strong>{{ patientName } as any}</strong> as dispensed?
117
+ Orders with no refills will be marked as completed.
118
+ </Trans>
119
+ </p>
120
+ {medicationRequestBundles.map((bundle) => (
121
+ <MedicationEvent key={bundle.request.id} medicationEvent={bundle.request} />
122
+ ))}
123
+ </ModalBody>
124
+ <ModalFooter>
125
+ <Button disabled={isSubmitting} kind="secondary" onClick={close}>
126
+ {t('createOrderWithoutDispensing', 'Create order without dispensing')}
127
+ </Button>
128
+ <Button
129
+ disabled={isSubmitting}
130
+ onClick={() => {
131
+ onConfirm();
132
+ }}>
133
+ {t('dispenseAllPrescriptions', 'Dispense all prescriptions')}
134
+ </Button>
135
+ </ModalFooter>
136
+ </>
137
+ );
138
+ };
139
+
140
+ export default OnPrescriptionFilledModal;
@@ -0,0 +1,7 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .modalDescription {
6
+ margin-bottom: layout.$spacing-05;
7
+ }