@kenyaemr/esm-imaging-orders-app 4.0.1-pre.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 (110) hide show
  1. package/.turbo/turbo-build.log +188 -0
  2. package/README.md +8 -0
  3. package/dist/123.js +2 -0
  4. package/dist/123.js.LICENSE.txt +40 -0
  5. package/dist/123.js.map +1 -0
  6. package/dist/144.js +2 -0
  7. package/dist/144.js.LICENSE.txt +19 -0
  8. package/dist/144.js.map +1 -0
  9. package/dist/225.js +1 -0
  10. package/dist/225.js.map +1 -0
  11. package/dist/300.js +1 -0
  12. package/dist/364.js +1 -0
  13. package/dist/364.js.map +1 -0
  14. package/dist/372.js +1 -0
  15. package/dist/372.js.map +1 -0
  16. package/dist/41.js +2 -0
  17. package/dist/41.js.LICENSE.txt +9 -0
  18. package/dist/41.js.map +1 -0
  19. package/dist/495.js +1 -0
  20. package/dist/495.js.map +1 -0
  21. package/dist/606.js +1 -0
  22. package/dist/606.js.map +1 -0
  23. package/dist/831.js +2 -0
  24. package/dist/831.js.LICENSE.txt +5 -0
  25. package/dist/831.js.map +1 -0
  26. package/dist/876.js +2 -0
  27. package/dist/876.js.LICENSE.txt +9 -0
  28. package/dist/876.js.map +1 -0
  29. package/dist/913.js +2 -0
  30. package/dist/913.js.LICENSE.txt +32 -0
  31. package/dist/913.js.map +1 -0
  32. package/dist/kenyaemr-esm-imaging-orders-app.js +1 -0
  33. package/dist/kenyaemr-esm-imaging-orders-app.js.buildmanifest.json +401 -0
  34. package/dist/kenyaemr-esm-imaging-orders-app.js.map +1 -0
  35. package/dist/main.js +2 -0
  36. package/dist/main.js.LICENSE.txt +60 -0
  37. package/dist/main.js.map +1 -0
  38. package/dist/routes.json +1 -0
  39. package/jest.config.js +8 -0
  40. package/package.json +55 -0
  41. package/src/config-schema.ts +51 -0
  42. package/src/constants.ts +3 -0
  43. package/src/declarations.d.ts +6 -0
  44. package/src/form/imaging-orders/add-imaging-orders/add-imaging-order.scss +44 -0
  45. package/src/form/imaging-orders/add-imaging-orders/add-imaging-order.workspace.tsx +86 -0
  46. package/src/form/imaging-orders/add-imaging-orders/imaging-order-form.component.tsx +354 -0
  47. package/src/form/imaging-orders/add-imaging-orders/imaging-order-form.scss +79 -0
  48. package/src/form/imaging-orders/add-imaging-orders/imaging-order.ts +19 -0
  49. package/src/form/imaging-orders/add-imaging-orders/imaging-type-search.scss +115 -0
  50. package/src/form/imaging-orders/add-imaging-orders/imaging-type-search.tsx +235 -0
  51. package/src/form/imaging-orders/add-imaging-orders/useImagingTypes.ts +89 -0
  52. package/src/form/imaging-orders/api.ts +230 -0
  53. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-icon.component.tsx +40 -0
  54. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-item-tile.component.tsx +96 -0
  55. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-item-tile.scss +72 -0
  56. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-panel.extension.tsx +191 -0
  57. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-panel.scss +74 -0
  58. package/src/form/imaging-orders/useOrderConfig.ts +48 -0
  59. package/src/form/imaging-report-form/imaging-report-form.component.tsx +161 -0
  60. package/src/form/imaging-report-form/imaging-report-form.scss +30 -0
  61. package/src/form/imaging-report-form/imaging.resource.ts +360 -0
  62. package/src/header/imagining-header.component.tsx +17 -0
  63. package/src/header/imagining-header.scss +5 -0
  64. package/src/hooks/useOrdersWorklist.ts +59 -0
  65. package/src/hooks/useSearchGroupedResults.ts +27 -0
  66. package/src/hooks/useSearchResults.ts +51 -0
  67. package/src/imaging-orders.component.tsx +14 -0
  68. package/src/imaging-tabs/approved/approved-orders.component.tsx +31 -0
  69. package/src/imaging-tabs/approved/approved-orders.scss +0 -0
  70. package/src/imaging-tabs/imaging-tabs.component.tsx +79 -0
  71. package/src/imaging-tabs/imaging-tabs.scss +5 -0
  72. package/src/imaging-tabs/orders-not-done/orders-not-done.component.tsx +42 -0
  73. package/src/imaging-tabs/referred-test/referred-ordered.component.tsx +26 -0
  74. package/src/imaging-tabs/referred-test/referred-ordered.scss +6 -0
  75. package/src/imaging-tabs/review-ordered/review-imaging-report-modal/review-imaging-report-dialog.component.tsx +138 -0
  76. package/src/imaging-tabs/review-ordered/review-imaging-report-modal/review-imaging-report-dialog.scss +5 -0
  77. package/src/imaging-tabs/review-ordered/review-ordered.component.tsx +28 -0
  78. package/src/imaging-tabs/review-ordered/review-ordered.scss +0 -0
  79. package/src/imaging-tabs/test-ordered/pick-imaging-order/add-to-worklist-dialog.component.tsx +94 -0
  80. package/src/imaging-tabs/test-ordered/pick-imaging-order/add-to-worklist-dialog.resource.ts +137 -0
  81. package/src/imaging-tabs/test-ordered/pick-imaging-order/add-to-worklist-dialog.scss +38 -0
  82. package/src/imaging-tabs/test-ordered/reject-order-dialog/radiology-reject-reason.component.tsx +40 -0
  83. package/src/imaging-tabs/test-ordered/reject-order-dialog/reject-order-dialog.component.tsx +95 -0
  84. package/src/imaging-tabs/test-ordered/reject-order-dialog/reject-order-dialog.scss +14 -0
  85. package/src/imaging-tabs/test-ordered/tests-ordered.component.tsx +35 -0
  86. package/src/imaging-tabs/test-ordered/tests-ordered.scss +13 -0
  87. package/src/imaging-tabs/test-ordered/transition-patient-new-queue/transition-latest-queue-entry-button.component.tsx +34 -0
  88. package/src/imaging-tabs/test-ordered/transition-patient-new-queue/transition-latest-queue-entry-button.scss +14 -0
  89. package/src/imaging-tabs/work-list/work-list.component.tsx +45 -0
  90. package/src/imaging-tabs/work-list/work-list.resource.ts +150 -0
  91. package/src/imaging-tabs/work-list/work-list.scss +207 -0
  92. package/src/index.ts +45 -0
  93. package/src/left-panel-link.tsx +42 -0
  94. package/src/root.component.tsx +19 -0
  95. package/src/routes.json +58 -0
  96. package/src/shared/imaging.resource.tsx +65 -0
  97. package/src/shared/ui/common/action-button/action-button.component.tsx +66 -0
  98. package/src/shared/ui/common/action-button/order-action-extension.component.tsx +21 -0
  99. package/src/shared/ui/common/grouped-imaging-types.ts +48 -0
  100. package/src/shared/ui/common/grouped-orders-table.component.tsx +154 -0
  101. package/src/shared/ui/common/grouped-orders-table.scss +13 -0
  102. package/src/shared/ui/common/list-order-details.component.tsx +72 -0
  103. package/src/shared/ui/common/list-order-details.scss +52 -0
  104. package/src/shared/ui/common/order-detail.component.tsx +14 -0
  105. package/src/shared/ui/common/order-detail.scss +14 -0
  106. package/src/types/index.ts +49 -0
  107. package/src/utils/functions.ts +238 -0
  108. package/translations/en.json +69 -0
  109. package/tsconfig.json +5 -0
  110. package/webpack.config.js +1 -0
@@ -0,0 +1,48 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { useMemo } from 'react';
3
+ import useSWRImmutable from 'swr/immutable';
4
+ import { type DurationUnit, type OrderFrequency } from '../../types';
5
+
6
+ export interface CommonConfigProps {
7
+ uuid: string;
8
+ display: string;
9
+ }
10
+
11
+ export interface OrderConfig {
12
+ durationUnits: Array<CommonConfigProps>;
13
+ orderFrequencies: Array<CommonConfigProps>;
14
+ }
15
+
16
+ export function useOrderConfig(): {
17
+ isLoading: boolean;
18
+ error: Error;
19
+ orderConfigObject: {
20
+ durationUnits: Array<DurationUnit>;
21
+ orderFrequencies: Array<OrderFrequency>;
22
+ };
23
+ } {
24
+ const { data, error, isLoading } = useSWRImmutable<{ data: OrderConfig }, Error>(
25
+ `${restBaseUrl}/orderentryconfig`,
26
+ openmrsFetch,
27
+ );
28
+
29
+ const results = useMemo(
30
+ () => ({
31
+ orderConfigObject: {
32
+ durationUnits: data?.data?.durationUnits?.map(({ uuid, display }) => ({
33
+ valueCoded: uuid,
34
+ value: display,
35
+ })),
36
+ orderFrequencies: data?.data?.orderFrequencies?.map(({ uuid, display }) => ({
37
+ valueCoded: uuid,
38
+ value: display,
39
+ })),
40
+ },
41
+ isLoading,
42
+ error,
43
+ }),
44
+ [data, error, isLoading],
45
+ );
46
+
47
+ return results;
48
+ }
@@ -0,0 +1,161 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import { mutate } from 'swr';
3
+ import styles from './imaging-report-form.scss';
4
+ import { useTranslation } from 'react-i18next';
5
+ import {
6
+ DefaultWorkspaceProps,
7
+ ExtensionSlot,
8
+ ResponsiveWrapper,
9
+ showNotification,
10
+ showSnackbar,
11
+ useLayoutType,
12
+ usePatient,
13
+ } from '@openmrs/esm-framework';
14
+ import { Result } from '../../imaging-tabs/work-list/work-list.resource';
15
+ import { Controller, useForm } from 'react-hook-form';
16
+ import { saveProcedureReport, useGetOrderConceptByUuid } from './imaging.resource';
17
+ import { Stack, Button, TextArea, ButtonSet, InlineLoading } from '@carbon/react';
18
+ import classNames from 'classnames';
19
+ import { z } from 'zod';
20
+ import { zodResolver } from '@hookform/resolvers/zod';
21
+
22
+ type ResultFormProps = DefaultWorkspaceProps & {
23
+ patientUuid: string;
24
+ order: Result;
25
+ };
26
+
27
+ const imagingReportSchema = z.object({
28
+ procedureReport: z.string({ required_error: 'Imaging report is required' }).min(1, {
29
+ message: 'Imaging report is required',
30
+ }),
31
+ });
32
+
33
+ type ImagingReportFormData = z.infer<typeof imagingReportSchema>;
34
+
35
+ const ImagingReportForm: React.FC<ResultFormProps> = ({
36
+ order,
37
+ patientUuid,
38
+ closeWorkspace,
39
+ closeWorkspaceWithSavedChanges,
40
+ promptBeforeClosing,
41
+ }) => {
42
+ const { t } = useTranslation();
43
+ const isTablet = useLayoutType() === 'tablet';
44
+ const { patient, isLoading } = usePatient(patientUuid);
45
+ const { concept, isLoading: isLoadingConcepts } = useGetOrderConceptByUuid(order.concept.uuid);
46
+ const {
47
+ formState: { isSubmitting, errors, isDirty },
48
+ control,
49
+ handleSubmit,
50
+ } = useForm<ImagingReportFormData>({
51
+ defaultValues: {
52
+ procedureReport: '',
53
+ },
54
+ resolver: zodResolver(imagingReportSchema),
55
+ mode: 'all',
56
+ });
57
+
58
+ const bannerState = useMemo(() => {
59
+ if (patient) {
60
+ return {
61
+ patient,
62
+ patientUuid,
63
+ hideActionsOverflow: true,
64
+ };
65
+ }
66
+ }, [patient, patientUuid]);
67
+
68
+ useEffect(() => {
69
+ if (promptBeforeClosing && isDirty) {
70
+ promptBeforeClosing(() => isDirty);
71
+ }
72
+ }, [promptBeforeClosing, isDirty, closeWorkspace]);
73
+
74
+ const onSubmit = async (formData: ImagingReportFormData) => {
75
+ const reportPayload = {
76
+ patient: patientUuid,
77
+ procedureOrder: order.uuid,
78
+ concept: order.concept.uuid,
79
+ status: 'COMPLETED',
80
+ procedureReport: formData.procedureReport,
81
+ encounters: [],
82
+ };
83
+
84
+ try {
85
+ const response = await saveProcedureReport(reportPayload);
86
+ if (response.ok) {
87
+ showSnackbar({
88
+ title: t('imagingOrderSaveSuccess', 'Imaging order saved successfully'),
89
+ kind: 'success',
90
+ subtitle: t(
91
+ 'imagingOrderSaveSuccessSubtitle',
92
+ 'Imaging order saved successfully. Report transitioned to awaiting approval.',
93
+ ),
94
+ isLowContrast: true,
95
+ });
96
+ closeWorkspaceWithSavedChanges();
97
+ mutate((key) => typeof key === 'string' && key.startsWith('/ws/rest/v1/order'), undefined, {
98
+ revalidate: true,
99
+ });
100
+ }
101
+ } catch (error) {
102
+ showNotification({
103
+ title: t('errorSavingReport', 'Error occurred while saving the report'),
104
+ kind: 'error',
105
+ critical: true,
106
+ description: error?.message,
107
+ });
108
+ }
109
+ };
110
+ return (
111
+ <>
112
+ {patient ? (
113
+ <ExtensionSlot name="patient-header-slot" state={bannerState} />
114
+ ) : (
115
+ <InlineLoading status="active" iconDescription="Loading" />
116
+ )}
117
+ <form aria-label="imaging form" className={styles.form} onSubmit={handleSubmit(onSubmit)}>
118
+ <div className={styles.formContainer}>
119
+ <Stack gap={7}>
120
+ <ResponsiveWrapper>
121
+ <Controller
122
+ control={control}
123
+ name="procedureReport"
124
+ render={({ field }) => (
125
+ <TextArea
126
+ labelText={concept?.display}
127
+ id="procedureReport"
128
+ name="procedureReport"
129
+ invalid={!!errors.procedureReport}
130
+ invalidText={errors.procedureReport?.message}
131
+ {...field}
132
+ />
133
+ )}
134
+ />
135
+ </ResponsiveWrapper>
136
+ </Stack>
137
+ </div>
138
+ <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
139
+ <Button style={{ maxWidth: '50%' }} kind="secondary" onClick={closeWorkspace}>
140
+ {t('cancel', 'Cancel')}
141
+ </Button>
142
+ <Button
143
+ disabled={isSubmitting || Object.keys(errors).length > 0}
144
+ style={{ maxWidth: '50%' }}
145
+ kind="primary"
146
+ type="submit">
147
+ {isSubmitting ? (
148
+ <span style={{ display: 'flex', justifyItems: 'center' }}>
149
+ {t('submitting', 'Submitting...')} <InlineLoading status="active" iconDescription="Loading" />
150
+ </span>
151
+ ) : (
152
+ t('saveAndClose', 'Save & close')
153
+ )}
154
+ </Button>
155
+ </ButtonSet>
156
+ </form>
157
+ </>
158
+ );
159
+ };
160
+
161
+ export default ImagingReportForm;
@@ -0,0 +1,30 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .form {
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: space-between;
9
+ height: 80%;
10
+ }
11
+
12
+ .formContainer {
13
+ margin: layout.$spacing-05;
14
+ }
15
+
16
+ .tablet {
17
+ padding: layout.$spacing-06 layout.$spacing-05;
18
+ background-color: colors.$white;
19
+ }
20
+
21
+ .desktop {
22
+ padding: 0;
23
+ position: absolute;
24
+ width: 100%;
25
+ bottom: 0;
26
+ }
27
+
28
+ .formStackControl {
29
+ row-gap: layout.$layout-01;
30
+ }
@@ -0,0 +1,360 @@
1
+ import { openmrsFetch } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { updateOrder } from '../../imaging-tabs/test-ordered/pick-imaging-order/add-to-worklist-dialog.resource';
4
+
5
+ export interface ConceptResponse {
6
+ uuid: string;
7
+ display: string;
8
+ name: Name;
9
+ datatype: Datatype;
10
+ conceptClass: ConceptClass;
11
+ set: boolean;
12
+ version: string;
13
+ retired: boolean;
14
+ names: Name2[];
15
+ descriptions: Description[];
16
+ mappings: Mapping[];
17
+ answers: any[];
18
+ setMembers: ConceptReference[];
19
+ auditInfo: AuditInfo;
20
+ attributes: any[];
21
+ links: Link18[];
22
+ resourceVersion: string;
23
+ hiNormal: number;
24
+ hiAbsolute: number;
25
+ hiCritical: number;
26
+ lowNormal: number;
27
+ lowAbsolute: number;
28
+ lowCritical: number;
29
+ }
30
+
31
+ export interface Name {
32
+ display: string;
33
+ uuid: string;
34
+ name: string;
35
+ locale: string;
36
+ localePreferred: boolean;
37
+ conceptNameType: string;
38
+ links: Link[];
39
+ resourceVersion: string;
40
+ }
41
+
42
+ export interface Link {
43
+ rel: string;
44
+ uri: string;
45
+ resourceAlias: string;
46
+ }
47
+
48
+ export interface Datatype {
49
+ uuid: string;
50
+ display: string;
51
+ name: string;
52
+ description: string;
53
+ hl7Abbreviation: string;
54
+ retired: boolean;
55
+ links: Link2[];
56
+ resourceVersion: string;
57
+ }
58
+
59
+ export interface Link2 {
60
+ rel: string;
61
+ uri: string;
62
+ resourceAlias: string;
63
+ }
64
+
65
+ export interface ConceptClass {
66
+ uuid: string;
67
+ display: string;
68
+ name: string;
69
+ description: string;
70
+ retired: boolean;
71
+ links: Link3[];
72
+ resourceVersion: string;
73
+ }
74
+
75
+ export interface Link3 {
76
+ rel: string;
77
+ uri: string;
78
+ resourceAlias: string;
79
+ }
80
+
81
+ export interface Name2 {
82
+ display: string;
83
+ uuid: string;
84
+ name: string;
85
+ locale: string;
86
+ localePreferred: boolean;
87
+ conceptNameType?: string;
88
+ links: Link4[];
89
+ resourceVersion: string;
90
+ }
91
+
92
+ export interface Link4 {
93
+ rel: string;
94
+ uri: string;
95
+ resourceAlias: string;
96
+ }
97
+
98
+ export interface Description {
99
+ display: string;
100
+ uuid: string;
101
+ description: string;
102
+ locale: string;
103
+ links: Link5[];
104
+ resourceVersion: string;
105
+ }
106
+
107
+ export interface Link5 {
108
+ rel: string;
109
+ uri: string;
110
+ resourceAlias: string;
111
+ }
112
+
113
+ export interface Mapping {
114
+ display: string;
115
+ uuid: string;
116
+ conceptReferenceTerm: ConceptReferenceTerm;
117
+ conceptMapType: ConceptMapType;
118
+ links: Link8[];
119
+ resourceVersion: string;
120
+ }
121
+
122
+ export interface ConceptReferenceTerm {
123
+ uuid: string;
124
+ display: string;
125
+ links: Link6[];
126
+ }
127
+
128
+ export interface Link6 {
129
+ rel: string;
130
+ uri: string;
131
+ resourceAlias: string;
132
+ }
133
+
134
+ export interface ConceptMapType {
135
+ uuid: string;
136
+ display: string;
137
+ links: Link7[];
138
+ }
139
+
140
+ export interface Link7 {
141
+ rel: string;
142
+ uri: string;
143
+ resourceAlias: string;
144
+ }
145
+
146
+ export interface Link8 {
147
+ rel: string;
148
+ uri: string;
149
+ resourceAlias: string;
150
+ }
151
+
152
+ export interface ConceptReference {
153
+ uuid: string;
154
+ display: string;
155
+ name: Name3;
156
+ datatype: Datatype2;
157
+ conceptClass: ConceptClass2;
158
+ set: boolean;
159
+ version: string;
160
+ retired: boolean;
161
+ names: Name4[];
162
+ descriptions: any[];
163
+ mappings: Mapping2[];
164
+ answers: Answer[];
165
+ setMembers: any[];
166
+ attributes: any[];
167
+ links: Link15[];
168
+ resourceVersion: string;
169
+ hiNormal: number;
170
+ hiAbsolute: number;
171
+ hiCritical: number;
172
+ lowNormal: number;
173
+ lowAbsolute: number;
174
+ lowCritical: number;
175
+ units?: string;
176
+ }
177
+
178
+ export interface Name3 {
179
+ display: string;
180
+ uuid: string;
181
+ name: string;
182
+ locale: string;
183
+ localePreferred: boolean;
184
+ conceptNameType: string;
185
+ links: Link9[];
186
+ resourceVersion: string;
187
+ }
188
+
189
+ export interface Link9 {
190
+ rel: string;
191
+ uri: string;
192
+ resourceAlias: string;
193
+ }
194
+
195
+ export interface Datatype2 {
196
+ uuid: string;
197
+ display: string;
198
+ links: Link10[];
199
+ }
200
+
201
+ export interface Link10 {
202
+ rel: string;
203
+ uri: string;
204
+ resourceAlias: string;
205
+ }
206
+
207
+ export interface ConceptClass2 {
208
+ uuid: string;
209
+ display: string;
210
+ links: Link11[];
211
+ }
212
+
213
+ export interface Link11 {
214
+ rel: string;
215
+ uri: string;
216
+ resourceAlias: string;
217
+ }
218
+
219
+ export interface Name4 {
220
+ uuid: string;
221
+ display: string;
222
+ links: Link12[];
223
+ }
224
+
225
+ export interface Link12 {
226
+ rel: string;
227
+ uri: string;
228
+ resourceAlias: string;
229
+ }
230
+
231
+ export interface Mapping2 {
232
+ uuid: string;
233
+ display: string;
234
+ links: Link13[];
235
+ }
236
+
237
+ export interface Link13 {
238
+ rel: string;
239
+ uri: string;
240
+ resourceAlias: string;
241
+ }
242
+
243
+ export interface Answer {
244
+ uuid: string;
245
+ display: string;
246
+ links: Link14[];
247
+ }
248
+
249
+ export interface Link14 {
250
+ rel: string;
251
+ uri: string;
252
+ resourceAlias: string;
253
+ }
254
+
255
+ export interface Link15 {
256
+ rel: string;
257
+ uri: string;
258
+ resourceAlias: string;
259
+ }
260
+
261
+ export interface AuditInfo {
262
+ creator: Creator;
263
+ dateCreated: string;
264
+ changedBy: ChangedBy;
265
+ dateChanged: string;
266
+ }
267
+
268
+ export interface Creator {
269
+ uuid: string;
270
+ display: string;
271
+ links: Link16[];
272
+ }
273
+
274
+ export interface Link16 {
275
+ rel: string;
276
+ uri: string;
277
+ resourceAlias: string;
278
+ }
279
+
280
+ export interface ChangedBy {
281
+ uuid: string;
282
+ display: string;
283
+ links: Link17[];
284
+ }
285
+
286
+ export interface Link17 {
287
+ rel: string;
288
+ uri: string;
289
+ resourceAlias: string;
290
+ }
291
+
292
+ export interface Link18 {
293
+ rel: string;
294
+ uri: string;
295
+ resourceAlias: string;
296
+ }
297
+
298
+ export interface ObPayload {
299
+ concept: string;
300
+ order: string;
301
+ value: string | number;
302
+ status: string;
303
+ }
304
+
305
+ // get order concept
306
+ export async function GetOrderConceptByUuid(uuid: string) {
307
+ const abortController = new AbortController();
308
+ return openmrsFetch(`/ws/rest/v1/concept/${uuid}?v=full`, {
309
+ headers: {
310
+ 'Content-Type': 'application/json',
311
+ },
312
+ signal: abortController.signal,
313
+ });
314
+ }
315
+
316
+ export function useGetOrderConceptByUuid(uuid: string) {
317
+ const apiUrl = `/ws/rest/v1/concept/${uuid}?v=custom:(uuid,display,name,datatype,set,answers,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units,setMembers:(uuid,display,answers,datatype,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units))`;
318
+
319
+ const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: ConceptResponse }, Error>(
320
+ apiUrl,
321
+ openmrsFetch,
322
+ );
323
+ return {
324
+ concept: data?.data,
325
+ isLoading,
326
+ isError: error,
327
+ isValidating,
328
+ mutate,
329
+ };
330
+ }
331
+
332
+ export async function UpdateEncounter(uuid: string, payload: any) {
333
+ const abortController = new AbortController();
334
+ return openmrsFetch(`/ws/rest/v1/encounter/${uuid}`, {
335
+ method: 'POST',
336
+ headers: {
337
+ 'Content-Type': 'application/json',
338
+ },
339
+ signal: abortController.signal,
340
+ body: payload,
341
+ });
342
+ }
343
+
344
+ export async function saveProcedureReport(reportPayload) {
345
+ const abortController = new AbortController();
346
+ const updateResults = await openmrsFetch(`/ws/rest/v1/procedure`, {
347
+ method: 'POST',
348
+ headers: {
349
+ 'Content-Type': 'application/json',
350
+ },
351
+ signal: abortController.signal,
352
+ body: reportPayload,
353
+ });
354
+
355
+ if (updateResults.status === 201 || updateResults.status === 200) {
356
+ return await updateOrder(reportPayload.procedureOrder, {
357
+ fulfillerStatus: 'COMPLETED',
358
+ });
359
+ }
360
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { PageHeader, LaboratoryPictogram } from '@openmrs/esm-framework';
4
+ import styles from './imagining-header.scss';
5
+
6
+ export const ImagingPageHeader: React.FC = () => {
7
+ const { t } = useTranslation();
8
+ // TODO Add correct illustration by registering the correct pictogram
9
+ // https://github.com/openmrs/openmrs-esm-core/blob/8d4612d384f000990303365e0d8575ebf382bb4f/packages/framework/esm-styleguide/src/pictograms/pictogram-registration.ts#L9
10
+ return (
11
+ <PageHeader
12
+ illustration={<LaboratoryPictogram />}
13
+ title={t('imagingOrders', 'Imaging Orders')}
14
+ className={styles.pageHeader}
15
+ />
16
+ );
17
+ };
@@ -0,0 +1,5 @@
1
+ @use '@carbon/colors';
2
+
3
+ .pageHeader {
4
+ border-bottom: 1px solid colors.$gray-20;
5
+ }
@@ -0,0 +1,59 @@
1
+ import { openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { useMemo, useCallback } from 'react';
4
+ import { Result } from '../imaging-tabs/work-list/work-list.resource';
5
+ import { ImagingConfig } from '../config-schema';
6
+ import { FulfillerStatus } from '../shared/ui/common/grouped-imaging-types';
7
+
8
+ const createApiUrl = (radiologyOrderTypeUuid: string, activatedOnOrAfterDate: string, fulfillerStatus: string) => {
9
+ const responseFormat =
10
+ 'custom:(uuid,orderNumber,patient:ref,concept:(uuid,display,conceptClass),action,careSetting,orderer:ref,urgency,instructions,bodySite,laterality,commentToFulfiller,procedures,display,fulfillerStatus,dateStopped,scheduledDate,dateActivated,fulfillerComment)';
11
+ const orderTypeParam = `orderTypes=${radiologyOrderTypeUuid}&activatedOnOrAfterDate=${activatedOnOrAfterDate}&isStopped=false&fulfillerStatus=${fulfillerStatus}&v=${responseFormat}`;
12
+ return `${restBaseUrl}/order?${orderTypeParam}`;
13
+ };
14
+
15
+ export function useOrdersWorkList(activatedOnOrAfterDate: string, fulfillerStatus: FulfillerStatus) {
16
+ const {
17
+ orders: { radiologyOrderTypeUuid },
18
+ radiologyConceptClassUuid,
19
+ } = useConfig<ImagingConfig>();
20
+
21
+ const apiUrl = useMemo(
22
+ () => createApiUrl(radiologyOrderTypeUuid, activatedOnOrAfterDate, fulfillerStatus),
23
+ [radiologyOrderTypeUuid, activatedOnOrAfterDate, fulfillerStatus],
24
+ );
25
+
26
+ const { data, error, isLoading, mutate } = useSWR<{ data: { results: Array<Result> } }>(apiUrl, openmrsFetch);
27
+
28
+ const filterOrders = useCallback(
29
+ (order: Result) => {
30
+ const baseConditions =
31
+ order.dateStopped === null && order.concept.conceptClass.uuid === radiologyConceptClassUuid;
32
+
33
+ switch (fulfillerStatus) {
34
+ case '':
35
+ return baseConditions && order.fulfillerStatus === null && order.action === 'NEW';
36
+ case 'IN_PROGRESS':
37
+ case 'DECLINED':
38
+ case 'COMPLETED':
39
+ case 'EXCEPTION':
40
+ return baseConditions && order.fulfillerStatus === fulfillerStatus && order.action !== 'DISCONTINUE';
41
+ default:
42
+ return false;
43
+ }
44
+ },
45
+ [radiologyConceptClassUuid, fulfillerStatus],
46
+ );
47
+
48
+ const sortedOrders = useMemo(() => {
49
+ const filteredOrders = data?.data?.results?.filter(filterOrders) || [];
50
+ return filteredOrders.sort((a, b) => new Date(a.dateActivated).getTime() - new Date(b.dateActivated).getTime());
51
+ }, [data, filterOrders]);
52
+
53
+ return {
54
+ workListEntries: sortedOrders,
55
+ isLoading,
56
+ isError: error,
57
+ mutate,
58
+ };
59
+ }
@@ -0,0 +1,27 @@
1
+ import { useMemo } from 'react';
2
+ import { GroupedOrders } from '../shared/ui/common/grouped-imaging-types';
3
+
4
+ /**
5
+ * A custom hook that filters grouped orders based on a search string.
6
+ *
7
+ * @param {Array<GroupedOrders>} data - An array of grouped orders to be filtered.
8
+ * @param {string} searchString - The string to search for within the orders.
9
+ * @returns {Array<GroupedOrders>} - The filtered array of grouped orders.
10
+ */
11
+ export function useSearchGroupedResults(data: Array<GroupedOrders>, searchString: string) {
12
+ return useMemo(() => {
13
+ const trimmedSearchString = searchString.trim().toLowerCase();
14
+
15
+ if (!trimmedSearchString) {
16
+ return data;
17
+ }
18
+
19
+ return data.filter((orderGroup) =>
20
+ orderGroup.orders.some(
21
+ (order) =>
22
+ order.orderNumber.toLowerCase().includes(trimmedSearchString) ||
23
+ order.patient.display.toLowerCase().includes(trimmedSearchString),
24
+ ),
25
+ );
26
+ }, [searchString, data]);
27
+ }