@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,71 @@
1
+ import useSWR from 'swr';
2
+ import { type OpenmrsResource, openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
3
+ import { type CodedProvider, type CodedCondition, ProcedurePayload } from '../../types';
4
+ import { updateOrder } from '../../procedures-ordered/pick-procedure-order/add-to-worklist-dialog.resource';
5
+
6
+ type Provider = {
7
+ uuid: string;
8
+ display: string;
9
+ person: OpenmrsResource;
10
+ };
11
+
12
+ export const useProviders = () => {
13
+ const url = `${restBaseUrl}/provider?v=custom:(uuid,display,person:(uuid,display))`;
14
+ const { data, error, isLoading } = useSWR<{
15
+ data: { results: Array<Provider> };
16
+ }>(url, openmrsFetch);
17
+
18
+ return {
19
+ providers: data?.data.results ?? [],
20
+ isLoadingProviders: isLoading,
21
+ loadingProvidersError: error,
22
+ };
23
+ };
24
+
25
+ export async function savePostProcedure(reportPayload) {
26
+ const abortController = new AbortController();
27
+ const updateResults = await openmrsFetch(`/ws/rest/v1/procedure`, {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ },
32
+ signal: abortController.signal,
33
+ body: reportPayload,
34
+ });
35
+
36
+ if (updateResults.status === 201 || updateResults.status === 200) {
37
+ return await updateOrder(reportPayload.procedureOrder, {
38
+ fulfillerStatus: 'COMPLETED',
39
+ });
40
+ }
41
+ }
42
+
43
+ export function useConditionsSearch(conditionToLookup: string) {
44
+ const config = useConfig();
45
+ const conditionConceptClassUuid = config?.conditionConceptClassUuid;
46
+ const conditionsSearchUrl = `${restBaseUrl}/conceptsearch?conceptClasses=${conditionConceptClassUuid}&q=${conditionToLookup}`;
47
+ const { data, error, isLoading } = useSWR<{ data: { results: Array<CodedCondition> } }, Error>(
48
+ conditionToLookup ? conditionsSearchUrl : null,
49
+ openmrsFetch,
50
+ );
51
+
52
+ return {
53
+ searchResults: data?.data?.results ?? [],
54
+ error: error,
55
+ isSearching: isLoading,
56
+ };
57
+ }
58
+
59
+ export function useProvidersSearch(providerToLookup: string) {
60
+ const providerSearchUrl = `${restBaseUrl}/provider?v=custom:(uuid,display,person:(uuid,display))&q=${providerToLookup}`;
61
+ const { data, error, isLoading } = useSWR<{ data: { results: Array<CodedProvider> } }, Error>(
62
+ providerToLookup ? providerSearchUrl : null,
63
+ openmrsFetch,
64
+ );
65
+
66
+ return {
67
+ providerSearchResults: data?.data?.results ?? [],
68
+ error: error,
69
+ isProviderSearching: isLoading,
70
+ };
71
+ }
@@ -0,0 +1,44 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @use '@openmrs/esm-styleguide/src/vars' as vars;
4
+
5
+ .container {
6
+ position: relative;
7
+ min-height: var(--desktop-workspace-window-height);
8
+ background-color: white;
9
+ display: flex;
10
+ flex-direction: column;
11
+ }
12
+
13
+ .patientHeader {
14
+ padding: spacing.$spacing-03 spacing.$spacing-05 spacing.$spacing-06;
15
+ background-color: vars.$ui-01;
16
+
17
+ span:first-of-type {
18
+ margin-right: spacing.$spacing-03;
19
+ }
20
+
21
+ .text02 {
22
+ color: vars.$text-02;
23
+ }
24
+ }
25
+
26
+ .backButton {
27
+ padding: spacing.$spacing-03;
28
+
29
+ button {
30
+ display: flex;
31
+ padding-left: 0 !important;
32
+ margin: 0 spacing.$spacing-05;
33
+
34
+ svg {
35
+ order: 1;
36
+ margin-right: spacing.$spacing-03;
37
+ margin-left: 0 !important;
38
+ }
39
+
40
+ span {
41
+ order: 2;
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,93 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import capitalize from 'lodash-es/capitalize';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Button } from '@carbon/react';
6
+ import { ArrowLeft } from '@carbon/react/icons';
7
+ import {
8
+ age,
9
+ formatDate,
10
+ parseDate,
11
+ useLayoutType,
12
+ usePatient,
13
+ type DefaultWorkspaceProps,
14
+ } from '@openmrs/esm-framework';
15
+ import { launchPatientWorkspace, type OrderBasketItem } from '@openmrs/esm-patient-common-lib';
16
+ import { TestTypeSearch } from './procedures-type-search';
17
+ import { ProceduresOrderForm } from './procedures-order-form.component';
18
+ import styles from './add-procedures-order.scss';
19
+ import { type ProcedureOrderBasketItem } from '../../../types';
20
+
21
+ export interface AddProcedureOrderWorkspaceAdditionalProps {
22
+ order?: OrderBasketItem;
23
+ }
24
+
25
+ export interface AddProceduresOrderWorkspace extends DefaultWorkspaceProps, AddProcedureOrderWorkspaceAdditionalProps {}
26
+
27
+ export default function AddProceduresOrderWorkspace({
28
+ order: initialOrder,
29
+ closeWorkspace,
30
+ closeWorkspaceWithSavedChanges,
31
+ promptBeforeClosing,
32
+ }: AddProceduresOrderWorkspace) {
33
+ const { t } = useTranslation();
34
+
35
+ const { patient, isLoading: isLoadingPatient } = usePatient();
36
+ const [currentLabOrder, setCurrentLabOrder] = useState(initialOrder as ProcedureOrderBasketItem);
37
+
38
+ const isTablet = useLayoutType() === 'tablet';
39
+
40
+ const patientName = `${patient?.name?.[0]?.given?.join(' ')} ${patient?.name?.[0].family}`;
41
+
42
+ const cancelOrder = useCallback(() => {
43
+ closeWorkspace({
44
+ ignoreChanges: true,
45
+ onWorkspaceClose: () => launchPatientWorkspace('order-basket'),
46
+ });
47
+ }, [closeWorkspace]);
48
+
49
+ return (
50
+ <div className={styles.container}>
51
+ {isTablet && !isLoadingPatient && (
52
+ <div className={styles.patientHeader}>
53
+ <span className={styles.bodyShort02}>{patientName}</span>
54
+ <span className={classNames(styles.text02, styles.bodyShort01)}>
55
+ {capitalize(patient?.gender)} &middot; {age(patient?.birthDate)} &middot;{' '}
56
+ <span>
57
+ {formatDate(parseDate(patient?.birthDate), {
58
+ mode: 'wide',
59
+ time: false,
60
+ })}
61
+ </span>
62
+ </span>
63
+ </div>
64
+ )}
65
+ {!isTablet && (
66
+ <div className={styles.backButton}>
67
+ <Button
68
+ kind="ghost"
69
+ renderIcon={(props) => <ArrowLeft size={24} {...props} />}
70
+ iconDescription="Return to order basket"
71
+ size="sm"
72
+ onClick={cancelOrder}>
73
+ <span>{t('backToOrderBasket', 'Back to order basket')}</span>
74
+ </Button>
75
+ </div>
76
+ )}
77
+ {!currentLabOrder ? (
78
+ <div>
79
+ <TestTypeSearch openLabForm={setCurrentLabOrder} />
80
+ </div>
81
+ ) : (
82
+ <div>
83
+ <ProceduresOrderForm
84
+ initialOrder={currentLabOrder}
85
+ closeWorkspace={closeWorkspace}
86
+ closeWorkspaceWithSavedChanges={closeWorkspaceWithSavedChanges}
87
+ promptBeforeClosing={promptBeforeClosing}
88
+ />
89
+ </div>
90
+ )}
91
+ </div>
92
+ );
93
+ }
@@ -0,0 +1,476 @@
1
+ import React, { useCallback, useEffect, useState, useMemo } from 'react';
2
+ import classNames from 'classnames';
3
+ import { launchPatientWorkspace, useOrderBasket } from '@openmrs/esm-patient-common-lib';
4
+ import {
5
+ translateFrom,
6
+ useLayoutType,
7
+ useSession,
8
+ useConfig,
9
+ ExtensionSlot,
10
+ type DefaultWorkspaceProps,
11
+ } from '@openmrs/esm-framework';
12
+ import { careSettingUuid, prepProceduresOrderPostData, useOrderReasons, useConceptById, type Concept } from '../api';
13
+ import {
14
+ Button,
15
+ ButtonSet,
16
+ Column,
17
+ ComboBox,
18
+ DatePicker,
19
+ DatePickerInput,
20
+ Form,
21
+ Layer,
22
+ Grid,
23
+ InlineNotification,
24
+ TextArea,
25
+ NumberInput,
26
+ } from '@carbon/react';
27
+ import { useTranslation } from 'react-i18next';
28
+ import { priorityOptions } from './procedures-order';
29
+ import { useProceduresTypes } from './useProceduresTypes';
30
+ import { Controller, type FieldErrors, useForm } from 'react-hook-form';
31
+ import { zodResolver } from '@hookform/resolvers/zod';
32
+ import { z } from 'zod';
33
+ import { type ConfigObject } from '../../../config-schema';
34
+ import styles from './procedures-order-form.scss';
35
+ import type { ProcedureOrderBasketItem, OrderFrequency } from '../../../types';
36
+ import { useOrderConfig } from '../order-config';
37
+ import { moduleName } from '../../../constants';
38
+
39
+ export interface ProceduresOrderFormProps {
40
+ initialOrder: ProcedureOrderBasketItem;
41
+ closeWorkspace: DefaultWorkspaceProps['closeWorkspace'];
42
+ closeWorkspaceWithSavedChanges: DefaultWorkspaceProps['closeWorkspaceWithSavedChanges'];
43
+ promptBeforeClosing: DefaultWorkspaceProps['promptBeforeClosing'];
44
+ }
45
+
46
+ export function ProceduresOrderForm({
47
+ initialOrder,
48
+ closeWorkspace,
49
+ closeWorkspaceWithSavedChanges,
50
+ promptBeforeClosing,
51
+ }: ProceduresOrderFormProps) {
52
+ const { t } = useTranslation();
53
+ const isTablet = useLayoutType() === 'tablet';
54
+ const session = useSession();
55
+ const { orderConfigObject, isLoading: isLoadingOrderConfig, error: errorFetchingOrderConfig } = useOrderConfig();
56
+ const { orders, setOrders } = useOrderBasket<ProcedureOrderBasketItem>('procedures', prepProceduresOrderPostData);
57
+ const { testTypes, isLoading: isLoadingTestTypes, error: errorLoadingTestTypes } = useProceduresTypes();
58
+ const [showErrorNotification, setShowErrorNotification] = useState(false);
59
+ const {
60
+ items: { answers: specimenSourceItems },
61
+ isLoading: isLoadingSpecimenSourceItems,
62
+ isError: errorFetchingSpecimenSourceItems,
63
+ } = useConceptById('159959AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
64
+ const {
65
+ items: { setMembers: specimenTypeItems },
66
+ isLoading: isLoadingSpecimenTypeItems,
67
+ isError: errorFetchingSpecimenTypeItems,
68
+ } = useConceptById('162476AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
69
+ const config = useConfig<ConfigObject>();
70
+ const orderReasonRequired = (
71
+ config.labTestsWithOrderReasons?.find((c) => c.labTestUuid === initialOrder?.testType?.conceptUuid) || {}
72
+ ).required;
73
+
74
+ const {
75
+ items: { answers: bodySiteItems },
76
+ } = useConceptById('ccf643d9-e525-499f-ae81-0e660d97c3e4');
77
+
78
+ const proceduresOrderFormSchema = z.object({
79
+ instructions: z.string().optional(),
80
+ urgency: z.string().refine((value) => value !== '', {
81
+ message: translateFrom(moduleName, 'addLabOrderPriorityRequired', 'Priority is required'),
82
+ }),
83
+ labReferenceNumber: z.string().optional(),
84
+ testType: z.object(
85
+ { label: z.string(), conceptUuid: z.string() },
86
+ {
87
+ required_error: translateFrom(moduleName, 'addLabOrderLabTestTypeRequired', 'Test type is required'),
88
+ invalid_type_error: translateFrom(moduleName, 'addLabOrderLabReferenceRequired', 'Test type is required'),
89
+ },
90
+ ),
91
+ orderReason: orderReasonRequired
92
+ ? z
93
+ .string({
94
+ required_error: translateFrom(moduleName, 'addLabOrderLabOrderReasonRequired', 'Order reason is required'),
95
+ })
96
+ .refine(
97
+ (value) => !!value,
98
+ translateFrom(moduleName, 'addLabOrderLabOrderReasonRequired', 'Order reason is required'),
99
+ )
100
+ : z.string().optional(),
101
+ scheduleDate: z.union([z.string(), z.date(), z.string().optional()]),
102
+ commentsToFulfiller: z.string().optional(),
103
+ numberOfRepeats: z.string().optional(),
104
+ previousOrder: z.string().optional(),
105
+ frequency: z.string().optional(),
106
+ bodySite: z.string().optional(),
107
+ orderReasonNonCoded: z.string().min(1, {
108
+ message: translateFrom(moduleName, 'addOrderReasonRequired', 'Order reason is required'),
109
+ }),
110
+ });
111
+
112
+ const orderFrequencies: Array<OrderFrequency> = useMemo(
113
+ () => orderConfigObject?.orderFrequencies ?? [],
114
+ [orderConfigObject],
115
+ );
116
+
117
+ const {
118
+ control,
119
+ handleSubmit,
120
+ formState: { errors, defaultValues, isDirty },
121
+ } = useForm<ProcedureOrderBasketItem>({
122
+ mode: 'all',
123
+ resolver: zodResolver(proceduresOrderFormSchema),
124
+ defaultValues: {
125
+ ...initialOrder,
126
+ },
127
+ });
128
+
129
+ const orderReasonUuids =
130
+ (config.labTestsWithOrderReasons?.find((c) => c.labTestUuid === defaultValues?.testType?.conceptUuid) || {})
131
+ .orderReasons || [];
132
+
133
+ const { orderReasons } = useOrderReasons(orderReasonUuids);
134
+
135
+ const handleFormSubmission = useCallback(
136
+ (data: ProcedureOrderBasketItem) => {
137
+ data.action = 'NEW';
138
+ data.careSetting = careSettingUuid;
139
+ data.orderer = session.currentProvider.uuid;
140
+ const newOrders = [...orders];
141
+ const existingOrder = orders.find((order) => order.testType.conceptUuid == defaultValues.testType.conceptUuid);
142
+ const orderIndex = existingOrder ? orders.indexOf(existingOrder) : orders.length;
143
+ newOrders[orderIndex] = data;
144
+ setOrders(newOrders);
145
+ closeWorkspaceWithSavedChanges({
146
+ onWorkspaceClose: () => launchPatientWorkspace('order-basket'),
147
+ });
148
+ },
149
+ [orders, setOrders, closeWorkspace, session?.currentProvider?.uuid, defaultValues],
150
+ );
151
+
152
+ const cancelOrder = useCallback(() => {
153
+ setOrders(orders.filter((order) => order.testType.conceptUuid !== defaultValues.testType.conceptUuid));
154
+ closeWorkspace({
155
+ onWorkspaceClose: () => launchPatientWorkspace('order-basket'),
156
+ });
157
+ }, [closeWorkspace, orders, setOrders, defaultValues]);
158
+
159
+ const onError = (errors: FieldErrors<ProcedureOrderBasketItem>) => {
160
+ if (errors) {
161
+ setShowErrorNotification(true);
162
+ }
163
+ };
164
+
165
+ useEffect(() => {
166
+ promptBeforeClosing(() => isDirty);
167
+ }, [isDirty]);
168
+
169
+ const [showScheduleDate, setShowScheduleDate] = useState(false);
170
+
171
+ return (
172
+ <>
173
+ {errorLoadingTestTypes && (
174
+ <InlineNotification
175
+ kind="error"
176
+ lowContrast
177
+ className={styles.inlineNotification}
178
+ title={t('errorLoadingTestTypes', 'Error occured when loading test types')}
179
+ subtitle={t('tryReopeningTheForm', 'Please try launching the form again')}
180
+ />
181
+ )}
182
+ <Form className={styles.orderForm} onSubmit={handleSubmit(handleFormSubmission, onError)} id="procedureOrderForm">
183
+ <div className={styles.form}>
184
+ <ExtensionSlot name="top-of-procedure-order-form-slot" state={{ order: initialOrder }} />
185
+
186
+ <Grid className={styles.gridRow}>
187
+ <Column lg={16} md={8} sm={4}>
188
+ <InputWrapper>
189
+ <Controller
190
+ name="testType"
191
+ control={control}
192
+ render={({ field: { onChange, onBlur, value } }) => (
193
+ <ComboBox
194
+ size="lg"
195
+ id="testTypeInput"
196
+ titleText={t('testType', 'Test type')}
197
+ selectedItem={value}
198
+ items={testTypes ?? []}
199
+ placeholder={
200
+ isLoadingTestTypes ? `${t('loading', 'Loading')}...` : t('testTypePlaceholder', 'Select one')
201
+ }
202
+ onBlur={onBlur}
203
+ disabled={isLoadingTestTypes}
204
+ onChange={({ selectedItem }) => onChange(selectedItem)}
205
+ invalid={errors.testType?.message}
206
+ invalidText={errors.testType?.message}
207
+ />
208
+ )}
209
+ />
210
+ </InputWrapper>
211
+ </Column>
212
+ </Grid>
213
+ <Grid className={styles.gridRow}>
214
+ <Column lg={8} md={8} sm={4}>
215
+ <InputWrapper>
216
+ <Controller
217
+ name="urgency"
218
+ control={control}
219
+ render={({ field: { onChange, onBlur, value } }) => (
220
+ <ComboBox
221
+ size="lg"
222
+ id="priorityInput"
223
+ titleText={t('priority', 'Priority')}
224
+ selectedItem={priorityOptions.find((option) => option.value === value) || null}
225
+ items={priorityOptions ?? []}
226
+ onBlur={onBlur}
227
+ onChange={({ selectedItem }) => {
228
+ onChange(selectedItem?.value || '');
229
+ setShowScheduleDate(selectedItem?.label === 'Scheduled');
230
+ }}
231
+ invalid={errors.urgency?.message}
232
+ invalidText={errors.urgency?.message}
233
+ />
234
+ )}
235
+ />
236
+ </InputWrapper>
237
+ </Column>
238
+ </Grid>
239
+ {showScheduleDate && (
240
+ <Grid className={styles.gridRow}>
241
+ <Column lg={16} md={4} sm={4}>
242
+ <div className={styles.fullWidthDatePickerContainer}>
243
+ <InputWrapper>
244
+ <Controller
245
+ name="scheduleDate"
246
+ control={control}
247
+ render={({ field: { onBlur, value, onChange, ref } }) => (
248
+ <DatePicker
249
+ datePickerType="single"
250
+ value={value}
251
+ onChange={([newStartDate]) => onChange(newStartDate)}
252
+ onBlur={onBlur}
253
+ ref={ref}>
254
+ <DatePickerInput
255
+ id="scheduleDatePicker"
256
+ placeholder="mm/dd/yyyy"
257
+ labelText={t('scheduleDate', 'Scheduled date')}
258
+ size="lg"
259
+ />
260
+ </DatePicker>
261
+ )}
262
+ />
263
+ </InputWrapper>
264
+ </div>
265
+ </Column>
266
+ </Grid>
267
+ )}
268
+ {orderReasons.length > 0 && (
269
+ <Grid className={styles.gridRow}>
270
+ <Column lg={16} md={8} sm={4}>
271
+ <InputWrapper>
272
+ <Controller
273
+ name="orderReason"
274
+ control={control}
275
+ render={({ field: { onChange, onBlur, value } }) => (
276
+ <ComboBox
277
+ size="lg"
278
+ id="orderReasonInput"
279
+ titleText={t('orderReason', 'Order reason')}
280
+ selectedItem={''}
281
+ itemToString={(item) => item?.display}
282
+ items={orderReasons ?? []}
283
+ onBlur={onBlur}
284
+ onChange={({ selectedItem }) => onChange(selectedItem?.uuid || '')}
285
+ invalid={errors.orderReason?.message}
286
+ invalidText={errors.orderReason?.message}
287
+ />
288
+ )}
289
+ />
290
+ </InputWrapper>
291
+ </Column>
292
+ </Grid>
293
+ )}
294
+ <Grid className={styles.gridRow}>
295
+ <Column lg={16} md={8} sm={4}>
296
+ <InputWrapper>
297
+ <Controller
298
+ name="bodySite"
299
+ control={control}
300
+ render={({ field: { onChange, onBlur, value } }) => (
301
+ <ComboBox
302
+ size="lg"
303
+ id="bodySiteInput"
304
+ titleText={t('bodySite', 'Body Site')}
305
+ selectedItem={bodySiteItems?.find((option) => option.uuid === value) || null}
306
+ items={bodySiteItems ?? []}
307
+ onBlur={onBlur}
308
+ onChange={({ selectedItem }) => onChange(selectedItem?.uuid || '')}
309
+ invalid={errors.bodySite?.message}
310
+ invalidText={errors.bodySite?.message}
311
+ itemToString={(item) => item?.display}
312
+ />
313
+ )}
314
+ />
315
+ </InputWrapper>
316
+ </Column>
317
+ </Grid>
318
+ <Grid className={styles.gridRow}>
319
+ <Column lg={16} md={8} sm={4}>
320
+ <InputWrapper>
321
+ <Controller
322
+ name="numberOfRepeats"
323
+ control={control}
324
+ render={({ field: { onChange, onBlur, value } }) => (
325
+ <NumberInput
326
+ enableCounter
327
+ id="numberOfRepeats"
328
+ label={t('numberOfRepeats', 'Number Of Repeats')}
329
+ min={0}
330
+ hideSteppers={false}
331
+ value={value}
332
+ onChange={onChange}
333
+ onBlur={onBlur}
334
+ invalid={errors.numberOfRepeats?.message}
335
+ invalidText={errors.numberOfRepeats?.message}
336
+ />
337
+ )}
338
+ />
339
+ </InputWrapper>
340
+ </Column>
341
+ </Grid>
342
+ <Grid className={styles.gridRow}>
343
+ <Column lg={16} md={8} sm={4}>
344
+ <InputWrapper>
345
+ <Controller
346
+ name="frequency"
347
+ control={control}
348
+ render={({ field: { onChange, onBlur, value } }) => (
349
+ <ComboBox
350
+ size="lg"
351
+ id="frequencyInput"
352
+ titleText={t('frequency', 'Frequency')}
353
+ selectedItem={orderFrequencies.find((option) => option.value === value) || null}
354
+ items={orderFrequencies ?? []}
355
+ onBlur={onBlur}
356
+ onChange={({ selectedItem }) => onChange(selectedItem?.value || '')}
357
+ invalid={errors.frequency?.message}
358
+ invalidText={errors.frequency?.message}
359
+ itemToString={(item) => item?.value}
360
+ disabled={isLoadingOrderConfig}
361
+ placeholder={
362
+ isLoadingOrderConfig ? `${t('loading', 'Loading')}...` : t('testTypePlaceholder', 'Select one')
363
+ }
364
+ />
365
+ )}
366
+ />
367
+ </InputWrapper>
368
+ </Column>
369
+ </Grid>
370
+ <Grid className={styles.gridRow}>
371
+ <Column lg={16} md={8} sm={4}>
372
+ <InputWrapper>
373
+ <Controller
374
+ name="orderReasonNonCoded"
375
+ control={control}
376
+ render={({ field: { onChange, onBlur, value } }) => (
377
+ <TextArea
378
+ enableCounter
379
+ id="orderReasonNonCodedInput"
380
+ size="lg"
381
+ labelText={'Order Reason'}
382
+ value={value}
383
+ onChange={onChange}
384
+ onBlur={onBlur}
385
+ maxCount={500}
386
+ invalid={errors.orderReasonNonCoded?.message}
387
+ invalidText={errors.orderReasonNonCoded?.message}
388
+ />
389
+ )}
390
+ />
391
+ </InputWrapper>
392
+ </Column>
393
+ </Grid>
394
+ <Grid className={styles.gridRow}>
395
+ <Column lg={16} md={8} sm={4}>
396
+ <InputWrapper>
397
+ <Controller
398
+ name="instructions"
399
+ control={control}
400
+ render={({ field: { onChange, onBlur, value } }) => (
401
+ <TextArea
402
+ enableCounter
403
+ id="additionalInstructionsInput"
404
+ size="lg"
405
+ labelText={t('additionalInstructions', 'Additional instructions')}
406
+ value={value}
407
+ onChange={onChange}
408
+ onBlur={onBlur}
409
+ maxCount={500}
410
+ invalid={errors.instructions?.message}
411
+ invalidText={errors.instructions?.message}
412
+ />
413
+ )}
414
+ />
415
+ </InputWrapper>
416
+ </Column>
417
+ </Grid>
418
+ <Grid className={styles.gridRow}>
419
+ <Column lg={16} md={8} sm={4}>
420
+ <InputWrapper>
421
+ <Controller
422
+ name="commentsToFulfiller"
423
+ control={control}
424
+ render={({ field: { onChange, onBlur, value } }) => (
425
+ <TextArea
426
+ enableCounter
427
+ id="commentsToFulfillerInput"
428
+ size="lg"
429
+ labelText={t('commentsToFulfiller', 'Comments To Fulfiller')}
430
+ value={value}
431
+ onChange={onChange}
432
+ onBlur={onBlur}
433
+ maxCount={500}
434
+ invalid={errors.commentsToFulfiller?.message}
435
+ invalidText={errors.commentsToFulfiller?.message}
436
+ />
437
+ )}
438
+ />
439
+ </InputWrapper>
440
+ </Column>
441
+ </Grid>
442
+ </div>
443
+ <div>
444
+ {showErrorNotification && (
445
+ <Column className={styles.errorContainer}>
446
+ <InlineNotification
447
+ lowContrast
448
+ title={t('error', 'Error')}
449
+ subtitle={t('pleaseRequiredFields', 'Please fill all required fields') + '.'}
450
+ onClose={() => setShowErrorNotification(false)}
451
+ />
452
+ </Column>
453
+ )}
454
+ <ButtonSet
455
+ className={classNames(styles.buttonSet, isTablet ? styles.tabletButtonSet : styles.desktopButtonSet)}>
456
+ <Button className={styles.button} kind="secondary" onClick={cancelOrder} size="xl">
457
+ {t('discard', 'Discard')}
458
+ </Button>
459
+ <Button className={styles.button} kind="primary" type="submit" size="xl">
460
+ {t('saveOrder', 'Save order')}
461
+ </Button>
462
+ </ButtonSet>
463
+ </div>
464
+ </Form>
465
+ </>
466
+ );
467
+ }
468
+
469
+ function InputWrapper({ children }) {
470
+ const isTablet = useLayoutType() === 'tablet';
471
+ return (
472
+ <Layer level={isTablet ? 1 : 0}>
473
+ <div className={styles.field}>{children}</div>
474
+ </Layer>
475
+ );
476
+ }