@thrustdevs/esm-procedure-orders-app 1.0.2-pre.6

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 (207) hide show
  1. package/.turbo/turbo-build.log +41 -0
  2. package/README.md +7 -0
  3. package/dist/122.js +1 -0
  4. package/dist/122.js.map +1 -0
  5. package/dist/144.js +2 -0
  6. package/dist/144.js.LICENSE.txt +19 -0
  7. package/dist/144.js.map +1 -0
  8. package/dist/182.js +1 -0
  9. package/dist/182.js.map +1 -0
  10. package/dist/205.js +1 -0
  11. package/dist/205.js.map +1 -0
  12. package/dist/216.js +2 -0
  13. package/dist/216.js.LICENSE.txt +9 -0
  14. package/dist/216.js.map +1 -0
  15. package/dist/290.js +2 -0
  16. package/dist/290.js.LICENSE.txt +5 -0
  17. package/dist/290.js.map +1 -0
  18. package/dist/300.js +1 -0
  19. package/dist/341.js +2 -0
  20. package/dist/341.js.LICENSE.txt +29 -0
  21. package/dist/341.js.map +1 -0
  22. package/dist/41.js +2 -0
  23. package/dist/41.js.LICENSE.txt +9 -0
  24. package/dist/41.js.map +1 -0
  25. package/dist/470.js +1 -0
  26. package/dist/470.js.map +1 -0
  27. package/dist/495.js +1 -0
  28. package/dist/495.js.map +1 -0
  29. package/dist/506.js +2 -0
  30. package/dist/506.js.LICENSE.txt +39 -0
  31. package/dist/506.js.map +1 -0
  32. package/dist/537.js +1 -0
  33. package/dist/537.js.map +1 -0
  34. package/dist/647.js +2 -0
  35. package/dist/647.js.LICENSE.txt +5 -0
  36. package/dist/647.js.map +1 -0
  37. package/dist/7.js +1 -0
  38. package/dist/7.js.map +1 -0
  39. package/dist/719.js +2 -0
  40. package/dist/719.js.LICENSE.txt +5 -0
  41. package/dist/719.js.map +1 -0
  42. package/dist/720.js +1 -0
  43. package/dist/720.js.map +1 -0
  44. package/dist/876.js +1 -0
  45. package/dist/876.js.map +1 -0
  46. package/dist/883.js +1 -0
  47. package/dist/883.js.map +1 -0
  48. package/dist/89.js +1 -0
  49. package/dist/89.js.map +1 -0
  50. package/dist/892.js +1 -0
  51. package/dist/892.js.map +1 -0
  52. package/dist/895.js +1 -0
  53. package/dist/895.js.map +1 -0
  54. package/dist/913.js +2 -0
  55. package/dist/913.js.LICENSE.txt +32 -0
  56. package/dist/913.js.map +1 -0
  57. package/dist/924.js +1 -0
  58. package/dist/924.js.map +1 -0
  59. package/dist/943.js +1 -0
  60. package/dist/943.js.map +1 -0
  61. package/dist/99.js +2 -0
  62. package/dist/99.js.LICENSE.txt +5 -0
  63. package/dist/99.js.map +1 -0
  64. package/dist/kenyaemr-esm-procedure-orders-app.js +1 -0
  65. package/dist/kenyaemr-esm-procedure-orders-app.js.buildmanifest.json +786 -0
  66. package/dist/kenyaemr-esm-procedure-orders-app.js.map +1 -0
  67. package/dist/main.js +2 -0
  68. package/dist/main.js.LICENSE.txt +35 -0
  69. package/dist/main.js.map +1 -0
  70. package/dist/routes.json +1 -0
  71. package/jest.config.js +8 -0
  72. package/package.json +55 -0
  73. package/src/completed-list/completed-list.component.tsx +40 -0
  74. package/src/completed-list/completed-list.resource.ts +0 -0
  75. package/src/completed-list/completed-list.scss +223 -0
  76. package/src/components/create-dashboard-link.component.tsx +35 -0
  77. package/src/components/overlay/hook.ts +47 -0
  78. package/src/components/overlay/overlay.component.tsx +42 -0
  79. package/src/components/overlay/overlay.scss +92 -0
  80. package/src/config-schema.ts +78 -0
  81. package/src/constants.ts +5 -0
  82. package/src/declarations.d.ts +6 -0
  83. package/src/empty-state/empty-state-component.tsx +21 -0
  84. package/src/empty-state/empty-state.scss +23 -0
  85. package/src/form/post-procedures/post-procedure-form.component.tsx +468 -0
  86. package/src/form/post-procedures/post-procedure-form.scss +189 -0
  87. package/src/form/post-procedures/post-procedure.resource.tsx +71 -0
  88. package/src/form/procedures-orders/add-procedures-order/add-procedures-order.scss +44 -0
  89. package/src/form/procedures-orders/add-procedures-order/add-procedures-order.workspace.tsx +93 -0
  90. package/src/form/procedures-orders/add-procedures-order/procedures-order-form.component.tsx +476 -0
  91. package/src/form/procedures-orders/add-procedures-order/procedures-order-form.scss +80 -0
  92. package/src/form/procedures-orders/add-procedures-order/procedures-order.ts +17 -0
  93. package/src/form/procedures-orders/add-procedures-order/procedures-type-search.scss +115 -0
  94. package/src/form/procedures-orders/add-procedures-order/procedures-type-search.tsx +236 -0
  95. package/src/form/procedures-orders/add-procedures-order/useProceduresTypes.ts +93 -0
  96. package/src/form/procedures-orders/api.ts +282 -0
  97. package/src/form/procedures-orders/order-config.ts +48 -0
  98. package/src/form/procedures-orders/procedures-order-basket-panel/procedures-icon.component.tsx +39 -0
  99. package/src/form/procedures-orders/procedures-order-basket-panel/procedures-order-basket-item-tile.component.tsx +100 -0
  100. package/src/form/procedures-orders/procedures-order-basket-panel/procedures-order-basket-item-tile.scss +72 -0
  101. package/src/form/procedures-orders/procedures-order-basket-panel/procedures-order-basket-panel.extension.tsx +190 -0
  102. package/src/form/procedures-orders/procedures-order-basket-panel/procedures-order-basket-panel.scss +74 -0
  103. package/src/form/procedures-orders/procedures-order-basket-panel/procedures-order-basket.scss +55 -0
  104. package/src/header/procedure-header.component.tsx +32 -0
  105. package/src/header/procedure-header.scss +70 -0
  106. package/src/header/procedure-illustration.component.tsx +52 -0
  107. package/src/hooks/useOrdersWorklist.ts +70 -0
  108. package/src/hooks/useSearchGroupedResults.ts +22 -0
  109. package/src/hooks/useSearchResults.ts +39 -0
  110. package/src/index.ts +59 -0
  111. package/src/left-panel-link.tsx +40 -0
  112. package/src/not-done-list/not-done-list.component.tsx +44 -0
  113. package/src/not-done-list/not-done.scss +207 -0
  114. package/src/patient-chart/patient-procedure-order-results-table.resource.ts +43 -0
  115. package/src/patient-chart/patient-procedure-order-results.component.tsx +12 -0
  116. package/src/patient-chart/patient-procedure-order-results.resource.ts +485 -0
  117. package/src/patient-chart/patient-procedure-results.component.tsx +30 -0
  118. package/src/patient-chart/procedure-active-order/procedure-active-order-results.component.tsx +390 -0
  119. package/src/patient-chart/procedure-active-order/procedure-active-order-results.scss +78 -0
  120. package/src/patient-chart/procedure-order-referals/procedure-order-referals.component.tsx +394 -0
  121. package/src/patient-chart/procedure-order-referals/procedure-order-referals.resource.tsx +0 -0
  122. package/src/patient-chart/procedure-order-referals/procedure-order-referals.scss +78 -0
  123. package/src/patient-chart/procedure-past-test/laboratory-past-test-order-results.component.tsx +366 -0
  124. package/src/patient-chart/procedure-past-test/laboratory-past-test-order-results.scss +74 -0
  125. package/src/patient-chart/procedure-tabs/laboratory-order-tabs.component.tsx +44 -0
  126. package/src/patient-chart/procedure-tabs/laboratory-order-tabs.scss +7 -0
  127. package/src/patient-chart/procedure-workspaces/laboratory-referral.workspace.component.tsx +11 -0
  128. package/src/patient-chart/procedure-workspaces/laboratory-referral.workspace.scss +0 -0
  129. package/src/patient-chart/results-summary/print-results-summary.component.tsx +152 -0
  130. package/src/patient-chart/results-summary/print-results-summary.scss +80 -0
  131. package/src/patient-chart/results-summary/print-results-table.component.tsx +134 -0
  132. package/src/patient-chart/results-summary/results-summary.resource.tsx +174 -0
  133. package/src/patient-chart/results-summary/results-summary.scss +158 -0
  134. package/src/patient-chart/results-summary/send-email-dialog.component.tsx +59 -0
  135. package/src/patient-chart/results-summary/test-children-results.component.tsx +177 -0
  136. package/src/patient-chart/results-summary/test-print-results-table.component.tsx +105 -0
  137. package/src/patient-chart/results-summary/test-results-table.component.tsx +103 -0
  138. package/src/print/print-procedure-results.component.tsx +49 -0
  139. package/src/print/print-procedure.component.tsx +105 -0
  140. package/src/print/print-procedure.scss +98 -0
  141. package/src/procedure-tabs/completed-tab.component.tsx +12 -0
  142. package/src/procedure-tabs/not-done-tab.component.tsx +12 -0
  143. package/src/procedure-tabs/referred-tab.component.tsx +12 -0
  144. package/src/procedure-tabs/work-list-tab.component.tsx +13 -0
  145. package/src/procedure.component.tsx +24 -0
  146. package/src/procedures-ordered/_pick-procedure-request-menu.component.tsx +33 -0
  147. package/src/procedures-ordered/pick-procedure-order/add-to-worklist-dialog.component.tsx +105 -0
  148. package/src/procedures-ordered/pick-procedure-order/add-to-worklist-dialog.resource.ts +106 -0
  149. package/src/procedures-ordered/pick-procedure-order/add-to-worklist-dialog.scss +38 -0
  150. package/src/procedures-ordered/pick-procedure-request-menu.component.tsx +32 -0
  151. package/src/procedures-ordered/procedure-dialogs/add-to-worklist-dialog.component.tsx +300 -0
  152. package/src/procedures-ordered/procedure-dialogs/add-to-worklist-dialog.resource.ts +153 -0
  153. package/src/procedures-ordered/procedure-dialogs/add-to-worklist-dialog.scss +38 -0
  154. package/src/procedures-ordered/procedure-instructions/instructions.scss +24 -0
  155. package/src/procedures-ordered/procedure-instructions/procedure-instructions-menu.component.tsx +32 -0
  156. package/src/procedures-ordered/procedure-instructions/procedure-instructions.component.tsx +78 -0
  157. package/src/procedures-ordered/procedure-instructions/procedure-instructions.scss +24 -0
  158. package/src/procedures-ordered/procedure-queue.scss +211 -0
  159. package/src/procedures-ordered/procedure-tabs.component.tsx +104 -0
  160. package/src/procedures-ordered/procedure-tests/procedure-tests.component.tsx +83 -0
  161. package/src/procedures-ordered/procedure-tests/procedure-tests.resource.ts +14 -0
  162. package/src/procedures-ordered/procedure-tests/procedure-tests.scss +12 -0
  163. package/src/procedures-ordered/procedures-ordered-list.component.tsx +38 -0
  164. package/src/procedures-ordered/reject-order-dialog/reject-order-dialog.scss +14 -0
  165. package/src/procedures-ordered/reject-order-dialog/reject-procedure-order-dialog.component.tsx +98 -0
  166. package/src/procedures-ordered/reject-reason/procedure-reject-reason-menu.component.tsx +32 -0
  167. package/src/procedures-ordered/reject-reason/procedure-reject-reason.component.tsx +40 -0
  168. package/src/procedures-ordered/transition-patient-new-queue/transition-latest-queue-entry-button.component.tsx +42 -0
  169. package/src/procedures-ordered/transition-patient-new-queue/transition-latest-queue-entry-button.scss +14 -0
  170. package/src/procedures-ordered/transition-patient-new-queue/transition-latest-queue-entry-button.test.tsx +67 -0
  171. package/src/referred-procedures/referred-procedures.component.tsx +37 -0
  172. package/src/results/result-form-field.component.tsx +141 -0
  173. package/src/results/result-form.component.tsx +120 -0
  174. package/src/results/result-form.resource.ts +361 -0
  175. package/src/results/result-form.scss +22 -0
  176. package/src/root.component.tsx +16 -0
  177. package/src/routes.json +152 -0
  178. package/src/setup-tests.ts +7 -0
  179. package/src/shared/ui/common/action-button/action-button.component.tsx +68 -0
  180. package/src/shared/ui/common/action-button/action-button.scss +12 -0
  181. package/src/shared/ui/common/action-button/order-action-extension.component.tsx +21 -0
  182. package/src/shared/ui/common/grouped-orders-table.component.tsx +176 -0
  183. package/src/shared/ui/common/grouped-orders-table.scss +30 -0
  184. package/src/shared/ui/common/grouped-procedure-types.ts +47 -0
  185. package/src/shared/ui/common/list-order-details.component.tsx +171 -0
  186. package/src/shared/ui/common/list-order-details.resource.ts +41 -0
  187. package/src/shared/ui/common/list-order-details.scss +118 -0
  188. package/src/shared/ui/common/orders-date-range-picker.scss +15 -0
  189. package/src/shared/ui/common/orders-date-range-picker.tsx +38 -0
  190. package/src/summary-tiles/procedure-summary-tiles.component.tsx +36 -0
  191. package/src/summary-tiles/procedure-summary-tiles.scss +11 -0
  192. package/src/summary-tiles/procedure-summary.resource.tsx +79 -0
  193. package/src/summary-tiles/summary-tile.component.tsx +41 -0
  194. package/src/summary-tiles/summary-tile.scss +53 -0
  195. package/src/types/index.ts +661 -0
  196. package/src/types/patient-queue.ts +77 -0
  197. package/src/ui-components/overflow-menu.component.tsx +74 -0
  198. package/src/ui-components/overflow-menu.scss +39 -0
  199. package/src/utils/functions.ts +236 -0
  200. package/src/utils/orders-table/orders-data-table.component.tsx +129 -0
  201. package/src/utils/orders-table/orders-data-table.scss +50 -0
  202. package/src/work-list/work-list.component.tsx +38 -0
  203. package/src/work-list/work-list.resource.ts +26 -0
  204. package/src/work-list/work-list.scss +207 -0
  205. package/translations/en.json +141 -0
  206. package/tsconfig.json +5 -0
  207. package/webpack.config.js +1 -0
@@ -0,0 +1,80 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @use '@carbon/colors';
4
+ @use '@openmrs/esm-styleguide/src/vars' as vars;
5
+
6
+ .orderForm {
7
+ flex-grow: 1;
8
+ display: flex;
9
+ flex-direction: column;
10
+ justify-content: space-between;
11
+
12
+ :global(.cds--css-grid) {
13
+ max-width: unset;
14
+ }
15
+
16
+ :global(.cds--css-grid-column) {
17
+ margin: 0 spacing.$spacing-03 !important;
18
+ }
19
+
20
+ :global(.cds--subgrid) {
21
+ margin: 0 calc(spacing.$spacing-03 * -1) !important;
22
+ }
23
+
24
+ :global(.cds--number--nosteppers.cds--number input[type='number']) {
25
+ padding-right: spacing.$spacing-05;
26
+ }
27
+
28
+ :global(.cds--number__control-btn) {
29
+ border: 0;
30
+ }
31
+ }
32
+
33
+ .form {
34
+ margin: spacing.$spacing-05;
35
+ }
36
+
37
+ .gridRow {
38
+ padding: 0px;
39
+
40
+ :global(.cds--number) {
41
+ display: inline-block;
42
+ }
43
+ }
44
+
45
+ .field {
46
+ margin-bottom: spacing.$spacing-05;
47
+ }
48
+
49
+ .buttonSet {
50
+ :global(.cds--btn) {
51
+ width: 50%;
52
+ max-width: unset;
53
+ }
54
+ }
55
+
56
+ .errorContainer {
57
+ margin: 1rem;
58
+ }
59
+
60
+ .errorNotification {
61
+ margin: 0.5rem 0;
62
+ }
63
+
64
+ /* Tablet */
65
+ :global(.omrs-breakpoint-lt-desktop) {
66
+ .orderForm {
67
+ height: calc(100vh - 6rem - var(--workspace-header-height));
68
+ background-color: #ededed;
69
+ }
70
+
71
+ .buttonSet {
72
+ padding: spacing.$spacing-06 spacing.$spacing-05;
73
+ background-color: vars.$ui-02;
74
+ }
75
+
76
+ .errorContainer {
77
+ margin: 1rem;
78
+ margin-bottom: calc(var(--bottom-nav-height) + 1rem);
79
+ }
80
+ }
@@ -0,0 +1,17 @@
1
+ import { type ProcedureOrderBasketItem } from '../../../types';
2
+ import { type ProceduresType } from './useProceduresTypes';
3
+
4
+ export const priorityOptions = [
5
+ { value: 'ROUTINE', label: 'Routine' },
6
+ { value: 'STAT', label: 'Stat' },
7
+ { value: 'ON_SCHEDULED_DATE', label: 'Scheduled' },
8
+ ];
9
+
10
+ export function createEmptyLabOrder(testType: ProceduresType, orderer: string): ProcedureOrderBasketItem {
11
+ return {
12
+ action: 'NEW',
13
+ display: testType.label,
14
+ testType,
15
+ orderer,
16
+ };
17
+ }
@@ -0,0 +1,115 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as vars;
4
+
5
+ /** For TestTypeSearchResults */
6
+
7
+ .container {
8
+ margin: spacing.$spacing-03 spacing.$spacing-05;
9
+ }
10
+
11
+ .divider {
12
+ border: 0;
13
+ border-top: 1px solid vars.$color-gray-30;
14
+ width: 66%;
15
+ }
16
+
17
+ .desktopDivider {
18
+ margin: spacing.$spacing-07 auto spacing.$spacing-05;
19
+ }
20
+
21
+ .tabletDivider {
22
+ margin: spacing.$spacing-08 auto spacing.$spacing-06;
23
+ }
24
+
25
+ .searchResultsCount {
26
+ @include type.type-style('body-compact-01');
27
+ color: vars.$text-02;
28
+ }
29
+
30
+ .orderBasketSearchResultsHeader {
31
+ display: flex;
32
+ justify-content: space-between;
33
+ margin-bottom: spacing.$spacing-03;
34
+ align-items: center;
35
+ }
36
+
37
+ /** For TestTypeSearchResultItem */
38
+
39
+ .searchResultTile {
40
+ padding: 0;
41
+ display: flex;
42
+ flex-direction: column;
43
+ justify-content: space-between;
44
+ border: 1px solid vars.$grey-2;
45
+ &:not(:last-of-type) {
46
+ margin-bottom: spacing.$spacing-03;
47
+ }
48
+ }
49
+
50
+ .tabletSearchResultTile {
51
+ margin-top: 5px;
52
+ background-color: vars.$ui-02;
53
+ }
54
+
55
+ .searchResultTileContent {
56
+ padding: spacing.$spacing-03 spacing.$spacing-05;
57
+ }
58
+
59
+ .searchResultActions {
60
+ border-top: 1px solid vars.$grey-2;
61
+ padding: 0 spacing.$spacing-05;
62
+ display: flex;
63
+ justify-content: flex-end;
64
+ }
65
+
66
+ .searchResultSkeletonWrapper {
67
+ margin: spacing.$spacing-03 1rem spacing.$spacing-03;
68
+
69
+ :global(.cds--skeleton__text) {
70
+ margin: 0;
71
+ }
72
+
73
+ .searchResultCntSkeleton {
74
+ margin-right: spacing.$spacing-07;
75
+ }
76
+
77
+ .skeletonTile {
78
+ display: flex;
79
+ align-items: center;
80
+ }
81
+ }
82
+
83
+ .emptyState {
84
+ margin: spacing.$spacing-05;
85
+ display: flex;
86
+ justify-content: center;
87
+ align-items: center;
88
+ padding: spacing.$spacing-07;
89
+ border: 1px solid vars.$ui-03;
90
+ text-align: center;
91
+ background-color: vars.$ui-01;
92
+ }
93
+
94
+ :global(.omrs-breakpoint-lt-small-desktop) .emptyState {
95
+ background-color: vars.$ui-02;
96
+ }
97
+
98
+ .link {
99
+ color: vars.$interactive-01;
100
+ text-decoration: underline;
101
+ cursor: pointer;
102
+ }
103
+
104
+ .resultsContainer {
105
+ overflow-y: scroll;
106
+ }
107
+
108
+ .resultsContainer::-webkit-scrollbar {
109
+ width: spacing.$spacing-03;
110
+ }
111
+
112
+ .resultsContainer::-webkit-scrollbar-thumb {
113
+ background: vars.$ui-04;
114
+ border-radius: spacing.$spacing-02;
115
+ }
@@ -0,0 +1,236 @@
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Button, ButtonSkeleton, Search, SkeletonText, Tile } from '@carbon/react';
5
+ import { ArrowRight, ShoppingCartArrowDown, ShoppingCartArrowUp } from '@carbon/react/icons';
6
+ import { useDebounce, useLayoutType, useSession, ResponsiveWrapper, closeWorkspace } from '@openmrs/esm-framework';
7
+ import { launchPatientWorkspace, useOrderBasket } from '@openmrs/esm-patient-common-lib';
8
+ import { prepProceduresOrderPostData } from '../api';
9
+ import { type ProceduresType, useProceduresTypes } from './useProceduresTypes';
10
+ import { createEmptyLabOrder } from './procedures-order';
11
+ import styles from './procedures-type-search.scss';
12
+ import { type ProcedureOrderBasketItem } from '../../../types';
13
+
14
+ export interface TestTypeSearchProps {
15
+ openLabForm: (searchResult: ProcedureOrderBasketItem) => void;
16
+ }
17
+
18
+ export function TestTypeSearch({ openLabForm }: TestTypeSearchProps) {
19
+ const { t } = useTranslation();
20
+ const isTablet = useLayoutType() === 'tablet';
21
+ const [searchTerm, setSearchTerm] = useState('');
22
+ const debouncedSearchTerm = useDebounce(searchTerm);
23
+ const searchInputRef = useRef(null);
24
+
25
+ const focusAndClearSearchInput = () => {
26
+ setSearchTerm('');
27
+ searchInputRef.current?.focus();
28
+ };
29
+
30
+ const handleSearchTermChange = (event: React.ChangeEvent<HTMLInputElement>) => {
31
+ setSearchTerm(event.target.value ?? '');
32
+ };
33
+
34
+ return (
35
+ <>
36
+ <ResponsiveWrapper>
37
+ <Search
38
+ autoFocus
39
+ size="lg"
40
+ placeholder={t('searchFieldPlaceholder', 'Search for a procedure')}
41
+ labelText={t('searchFieldPlaceholder', 'Search for a procedure')}
42
+ onChange={handleSearchTermChange}
43
+ ref={searchInputRef}
44
+ value={searchTerm}
45
+ />
46
+ </ResponsiveWrapper>
47
+ <TestTypeSearchResults
48
+ searchTerm={debouncedSearchTerm}
49
+ openOrderForm={openLabForm}
50
+ focusAndClearSearchInput={focusAndClearSearchInput}
51
+ />
52
+ </>
53
+ );
54
+ }
55
+
56
+ interface TestTypeSearchResultsProps {
57
+ searchTerm: string;
58
+ openOrderForm: (searchResult: ProcedureOrderBasketItem) => void;
59
+ focusAndClearSearchInput: () => void;
60
+ }
61
+
62
+ function TestTypeSearchResults({ searchTerm, openOrderForm, focusAndClearSearchInput }: TestTypeSearchResultsProps) {
63
+ const { t } = useTranslation();
64
+ const isTablet = useLayoutType() === 'tablet';
65
+ const { testTypes, isLoading, error } = useProceduresTypes(searchTerm);
66
+
67
+ if (isLoading) {
68
+ return <TestTypeSearchSkeleton />;
69
+ }
70
+
71
+ if (error) {
72
+ return (
73
+ <Tile className={styles.emptyState}>
74
+ <div>
75
+ <h4 className={styles.productiveHeading01}>
76
+ {t('errorFetchingTestTypes', 'Error fetching results for "{{searchTerm}}"', {
77
+ searchTerm,
78
+ })}
79
+ </h4>
80
+ <p className={styles.bodyShort01}>
81
+ <span>{t('trySearchingAgain', 'Please try searching again')}</span>
82
+ </p>
83
+ </div>
84
+ </Tile>
85
+ );
86
+ }
87
+
88
+ return (
89
+ <>
90
+ {testTypes?.length ? (
91
+ <div className={styles.container}>
92
+ {searchTerm && (
93
+ <div className={styles.orderBasketSearchResultsHeader}>
94
+ <span className={styles.searchResultsCount}>
95
+ {t('searchResultsMatchesForTerm', '{{count}} results for "{{searchTerm}}"', {
96
+ count: testTypes?.length,
97
+ searchTerm,
98
+ })}
99
+ </span>
100
+ <Button kind="ghost" onClick={focusAndClearSearchInput} size={isTablet ? 'md' : 'sm'}>
101
+ {t('clearSearchResults', 'Clear Results')}
102
+ </Button>
103
+ </div>
104
+ )}
105
+ <div className={styles.resultsContainer}>
106
+ {testTypes.map((testType) => (
107
+ <TestTypeSearchResultItem key={testType.conceptUuid} testType={testType} openOrderForm={openOrderForm} />
108
+ ))}
109
+ </div>
110
+ </div>
111
+ ) : (
112
+ <Tile className={styles.emptyState}>
113
+ <div>
114
+ <h4 className={styles.productiveHeading01}>
115
+ {t('noResultsForTestTypeSearch', 'No results to display for "{{searchTerm}}"', {
116
+ searchTerm,
117
+ })}
118
+ </h4>
119
+ <p className={styles.bodyShort01}>
120
+ <span>{t('tryTo', 'Try to')}</span>{' '}
121
+ <span className={styles.link} role="link" tabIndex={0} onClick={focusAndClearSearchInput}>
122
+ {t('searchAgain', 'search again')}
123
+ </span>{' '}
124
+ <span>{t('usingADifferentTerm', 'using a different term')}</span>
125
+ </p>
126
+ </div>
127
+ </Tile>
128
+ )}
129
+ <hr className={classNames(styles.divider, isTablet ? styles.tabletDivider : styles.desktopDivider)} />
130
+ </>
131
+ );
132
+ }
133
+
134
+ interface TestTypeSearchResultItemProps {
135
+ testType: ProceduresType;
136
+ openOrderForm: (searchResult: ProcedureOrderBasketItem) => void;
137
+ }
138
+
139
+ const TestTypeSearchResultItem: React.FC<TestTypeSearchResultItemProps> = ({ testType, openOrderForm }) => {
140
+ const isTablet = useLayoutType() === 'tablet';
141
+ const session = useSession();
142
+ const { orders, setOrders } = useOrderBasket<ProcedureOrderBasketItem>('procedures', prepProceduresOrderPostData);
143
+ const testTypeAlreadyInBasket = useMemo(
144
+ () => orders?.some((order) => order.testType.conceptUuid === testType.conceptUuid),
145
+ [orders, testType],
146
+ );
147
+
148
+ const createLabOrder = useCallback(
149
+ (testType: ProceduresType) => {
150
+ return createEmptyLabOrder(testType, session.currentProvider.uuid);
151
+ },
152
+ [session.currentProvider?.uuid],
153
+ );
154
+
155
+ const { t } = useTranslation();
156
+
157
+ const addToBasket = useCallback(() => {
158
+ const labOrder = createLabOrder(testType);
159
+ labOrder.isOrderIncomplete = true;
160
+ setOrders([...orders, labOrder]);
161
+ closeWorkspace('add-procedures-order', {
162
+ ignoreChanges: true,
163
+ onWorkspaceClose: () => launchPatientWorkspace('order-basket'),
164
+ });
165
+ }, [orders, setOrders, createLabOrder, testType]);
166
+
167
+ const removeFromBasket = useCallback(() => {
168
+ setOrders(orders.filter((order) => order.testType.conceptUuid !== testType.conceptUuid));
169
+ }, [orders, setOrders, testType.conceptUuid]);
170
+
171
+ return (
172
+ <Tile
173
+ className={classNames(styles.searchResultTile, {
174
+ [styles.tabletSearchResultTile]: isTablet,
175
+ })}
176
+ key={testType.conceptUuid}
177
+ role="listitem">
178
+ <div className={classNames(styles.searchResultTileContent, styles.text02)}>
179
+ <p>
180
+ <span className={styles.productiveHeading01}>{testType.label}</span>{' '}
181
+ </p>
182
+ </div>
183
+ <div className={styles.searchResultActions}>
184
+ {testTypeAlreadyInBasket ? (
185
+ <Button
186
+ kind="danger--ghost"
187
+ renderIcon={(props) => <ShoppingCartArrowUp size={16} {...props} />}
188
+ onClick={() => removeFromBasket()}>
189
+ {t('removeFromBasket', 'Remove from basket')}
190
+ </Button>
191
+ ) : (
192
+ <Button
193
+ kind="ghost"
194
+ renderIcon={(props) => <ShoppingCartArrowDown size={16} {...props} />}
195
+ onClick={() => addToBasket()}>
196
+ {t('directlyAddToBasket', 'Add to basket')}
197
+ </Button>
198
+ )}
199
+ <Button
200
+ kind="ghost"
201
+ renderIcon={(props) => <ArrowRight size={16} {...props} />}
202
+ onClick={() => openOrderForm(createLabOrder(testType))}>
203
+ {t('goToDrugOrderForm', 'Order form')}
204
+ </Button>
205
+ </div>
206
+ </Tile>
207
+ );
208
+ };
209
+
210
+ const TestTypeSearchSkeleton = () => {
211
+ const isTablet = useLayoutType() === 'tablet';
212
+ const tileClassName = `${isTablet ? `${styles.tabletSearchResultTile}` : `${styles.desktopSearchResultTile}`} ${
213
+ styles.skeletonTile
214
+ }`;
215
+ return (
216
+ <div className={styles.searchResultSkeletonWrapper}>
217
+ <div className={styles.orderBasketSearchResultsHeader}>
218
+ <SkeletonText className={styles.searchResultCntSkeleton} />
219
+ <ButtonSkeleton size={isTablet ? 'md' : 'sm'} />
220
+ </div>
221
+ <Tile className={tileClassName}>
222
+ <SkeletonText />
223
+ </Tile>
224
+ <Tile className={tileClassName}>
225
+ <SkeletonText />
226
+ </Tile>
227
+ <Tile className={tileClassName}>
228
+ <SkeletonText />
229
+ </Tile>
230
+ <Tile className={tileClassName}>
231
+ <SkeletonText />
232
+ </Tile>
233
+ <hr className={classNames(styles.divider, isTablet ? styles.tabletDivider : styles.desktopDivider)} />
234
+ </div>
235
+ );
236
+ };
@@ -0,0 +1,93 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import useSWRImmutable from 'swr/immutable';
3
+ import fuzzy from 'fuzzy';
4
+ import { type FetchResponse, openmrsFetch, useConfig, restBaseUrl, reportError } from '@openmrs/esm-framework';
5
+ import { type ConfigObject } from '../../../config-schema';
6
+ import { type Concept } from '../api';
7
+
8
+ type ConceptResult = FetchResponse<Concept>;
9
+ type ConceptResults = FetchResponse<{ setMembers: Array<Concept> }>;
10
+
11
+ export interface ProceduresType {
12
+ label: string;
13
+ conceptUuid: string;
14
+ }
15
+
16
+ export interface UseProceduresType {
17
+ testTypes: Array<ProceduresType>;
18
+ isLoading: boolean;
19
+ error: Error;
20
+ }
21
+
22
+ function openmrsFetchMultiple(urls: Array<string>) {
23
+ // SWR has an RFC for `useSWRList`:
24
+ // https://github.com/vercel/swr/discussions/1988
25
+ // If that ever is implemented we should switch to using that.
26
+ return Promise.all(urls.map((url) => openmrsFetch<{ results: Array<Concept> }>(url)));
27
+ }
28
+
29
+ function useProceduresConceptsSWR(labOrderableConcepts?: Array<string>) {
30
+ const config = useConfig<ConfigObject>();
31
+ const { data, isLoading, error } = useSWRImmutable(
32
+ () =>
33
+ labOrderableConcepts
34
+ ? labOrderableConcepts.map((c) => `${restBaseUrl}/concept/${c}`)
35
+ : `${restBaseUrl}/concept/${config.procedureConceptSetUuid}?v=custom:setMembers`,
36
+ (labOrderableConcepts ? openmrsFetchMultiple : openmrsFetch) as any,
37
+ {
38
+ shouldRetryOnError(err) {
39
+ return err instanceof Response;
40
+ },
41
+ },
42
+ );
43
+
44
+ const results = useMemo(() => {
45
+ if (isLoading || error) {
46
+ return null;
47
+ }
48
+ return labOrderableConcepts
49
+ ? (data as Array<ConceptResult>)?.flatMap((d) => d.data.setMembers)
50
+ : ((data as ConceptResults)?.data.setMembers ?? ([] as Concept[]));
51
+ }, [data, isLoading, error]);
52
+
53
+ return {
54
+ data: results,
55
+ isLoading,
56
+ error,
57
+ };
58
+ }
59
+
60
+ export function useProceduresTypes(searchTerm = ''): UseProceduresType {
61
+ const {
62
+ orders: { labOrderableConcepts },
63
+ } = useConfig<ConfigObject>();
64
+
65
+ const { data, isLoading, error } = useProceduresConceptsSWR(
66
+ labOrderableConcepts.length ? labOrderableConcepts : null,
67
+ );
68
+
69
+ useEffect(() => {
70
+ if (error) {
71
+ reportError(error);
72
+ }
73
+ }, [error]);
74
+
75
+ const testConcepts = useMemo(() => {
76
+ return data?.map((concept) => ({
77
+ label: concept.display,
78
+ conceptUuid: concept.uuid,
79
+ }));
80
+ }, [data]);
81
+
82
+ const filteredTestTypes = useMemo(() => {
83
+ return searchTerm && !isLoading && !error
84
+ ? fuzzy.filter(searchTerm, testConcepts, { extract: (c) => c.label }).map((result) => result.original)
85
+ : testConcepts;
86
+ }, [testConcepts, searchTerm]);
87
+
88
+ return {
89
+ testTypes: filteredTestTypes,
90
+ isLoading: isLoading,
91
+ error: error,
92
+ };
93
+ }