@openmrs/esm-patient-tests-app 11.3.1-patch.9064 → 11.3.1-patch.9508
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +22 -22
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/{6231.js → 1477.js} +1 -1
- package/dist/1477.js.map +1 -0
- package/dist/1638.js +1 -1
- package/dist/1638.js.map +1 -1
- package/dist/1935.js +1 -1
- package/dist/1935.js.map +1 -1
- package/dist/2146.js +1 -1
- package/dist/2690.js +1 -1
- package/dist/3099.js +1 -1
- package/dist/34.js +1 -1
- package/dist/34.js.map +1 -1
- package/dist/3509.js +1 -1
- package/dist/3509.js.map +1 -1
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/439.js +1 -0
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4944.js +1 -1
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/5670.js +1 -1
- package/dist/5670.js.map +1 -1
- package/dist/6022.js +1 -1
- package/dist/6113.js +1 -0
- package/dist/6113.js.map +1 -0
- package/dist/6301.js +1 -1
- package/dist/6301.js.map +1 -1
- package/dist/6336.js +1 -0
- package/dist/6336.js.map +1 -0
- package/dist/6468.js +1 -1
- package/dist/6589.js +1 -0
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/7202.js +1 -0
- package/dist/7202.js.map +1 -0
- package/dist/723.js +1 -1
- package/dist/7617.js +1 -1
- package/dist/790.js +1 -1
- package/dist/790.js.map +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8307.js +2 -0
- package/dist/8307.js.map +1 -0
- package/dist/8349.js +1 -1
- package/dist/8371.js +1 -0
- package/dist/8555.js +2 -0
- package/dist/8555.js.map +1 -0
- package/dist/8618.js +1 -1
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-tests-app.js +1 -1
- package/dist/openmrs-esm-patient-tests-app.js.buildmanifest.json +319 -249
- package/dist/openmrs-esm-patient-tests-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +3 -3
- package/src/edit-test-results/modal/edit-lab-results.modal.tsx +6 -2
- package/src/index.ts +1 -1
- package/src/routes.json +2 -2
- package/src/test-orders/add-test-order/add-test-order.test.tsx +13 -10
- package/src/test-orders/add-test-order/add-test-order.workspace.tsx +43 -7
- package/src/test-orders/add-test-order/test-order-form.component.tsx +41 -7
- package/src/test-orders/add-test-order/test-type-search.component.tsx +56 -8
- package/src/test-orders/lab-order-basket-panel/lab-icon.component.tsx +27 -0
- package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.extension.tsx +62 -15
- package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.scss +26 -11
- package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.test.tsx +18 -5
- package/src/test-results/filter/filter-context.test.tsx +556 -0
- package/src/test-results/filter/filter-context.tsx +1 -1
- package/src/test-results/filter/filter-reducer.test.ts +540 -0
- package/src/test-results/filter/filter-reducer.ts +1 -1
- package/src/test-results/filter/filter-set.component.tsx +75 -48
- package/src/test-results/filter/filter-set.test.tsx +694 -0
- package/src/test-results/filter/filter-types.ts +24 -1
- package/src/test-results/grouped-timeline/grid.component.tsx +4 -2
- package/src/test-results/grouped-timeline/grouped-timeline.component.tsx +20 -22
- package/src/test-results/grouped-timeline/grouped-timeline.test.tsx +52 -2
- package/src/test-results/grouped-timeline/reference-range-helpers.test.ts +308 -0
- package/src/test-results/grouped-timeline/reference-range-helpers.ts +161 -0
- package/src/test-results/grouped-timeline/timeline-data-group.component.tsx +13 -6
- package/src/test-results/grouped-timeline/useObstreeData.test.ts +471 -0
- package/src/test-results/grouped-timeline/useObstreeData.ts +108 -13
- package/src/test-results/individual-results-table/individual-results-table.component.tsx +18 -6
- package/src/test-results/individual-results-table/individual-results-table.test.tsx +65 -3
- package/src/test-results/individual-results-table-tablet/helper.tsx +8 -2
- package/src/test-results/individual-results-table-tablet/individual-results-table-tablet.component.tsx +5 -5
- package/src/test-results/individual-results-table-tablet/lab-set-panel.component.tsx +2 -1
- package/src/test-results/individual-results-table-tablet/usePanelData.tsx +40 -26
- package/src/test-results/loadPatientTestData/helpers.test.ts +834 -0
- package/src/test-results/loadPatientTestData/helpers.ts +143 -12
- package/src/test-results/loadPatientTestData/loadPatientData.ts +66 -11
- package/src/test-results/loadPatientTestData/usePatientResultsData.ts +20 -9
- package/src/test-results/overview/common-datatable.component.tsx +1 -1
- package/src/test-results/overview/external-overview.extension.tsx +1 -2
- package/src/test-results/overview/useOverviewData.ts +22 -10
- package/src/test-results/print-modal/print-modal.extension.tsx +1 -1
- package/src/test-results/results-viewer/results-viewer.extension.tsx +12 -7
- package/src/test-results/tree-view/tree-view.component.tsx +31 -8
- package/src/test-results/tree-view/tree-view.test.tsx +119 -2
- package/src/test-results/trendline/trendline-resource.tsx +48 -5
- package/src/test-results/trendline/trendline.component.tsx +88 -52
- package/src/test-results/ui-elements/{resetFiltersEmptyState → reset-filters-empty-state}/filter-empty-data-illustration.tsx +2 -2
- package/src/test-results/ui-elements/{resetFiltersEmptyState → reset-filters-empty-state}/filter-empty-state.component.tsx +5 -6
- package/src/types.ts +20 -1
- package/translations/am.json +3 -4
- package/translations/ar.json +3 -4
- package/translations/ar_SY.json +3 -4
- package/translations/bn.json +3 -4
- package/translations/cs.json +119 -0
- package/translations/de.json +3 -4
- package/translations/en.json +3 -2
- package/translations/en_US.json +3 -4
- package/translations/es.json +3 -4
- package/translations/es_MX.json +3 -4
- package/translations/fr.json +5 -6
- package/translations/he.json +3 -4
- package/translations/hi.json +3 -4
- package/translations/hi_IN.json +3 -4
- package/translations/id.json +3 -4
- package/translations/it.json +3 -4
- package/translations/ka.json +3 -4
- package/translations/km.json +3 -4
- package/translations/ku.json +3 -4
- package/translations/ky.json +3 -4
- package/translations/lg.json +3 -4
- package/translations/ne.json +3 -4
- package/translations/pl.json +3 -4
- package/translations/pt.json +3 -4
- package/translations/pt_BR.json +3 -4
- package/translations/qu.json +3 -4
- package/translations/ro_RO.json +3 -4
- package/translations/ru_RU.json +3 -4
- package/translations/si.json +3 -4
- package/translations/sq.json +119 -0
- package/translations/sw.json +3 -4
- package/translations/sw_KE.json +3 -4
- package/translations/tr.json +3 -4
- package/translations/tr_TR.json +3 -4
- package/translations/uk.json +3 -4
- package/translations/uz.json +3 -4
- package/translations/uz@Latn.json +3 -4
- package/translations/uz_UZ.json +3 -4
- package/translations/vi.json +3 -4
- package/translations/zh.json +3 -4
- package/translations/zh_CN.json +3 -4
- package/translations/zh_TW.json +119 -0
- package/dist/1479.js +0 -1
- package/dist/1479.js.map +0 -1
- package/dist/2537.js +0 -1
- package/dist/2537.js.map +0 -1
- package/dist/4918.js +0 -1
- package/dist/4918.js.map +0 -1
- package/dist/5836.js +0 -2
- package/dist/5836.js.map +0 -1
- package/dist/6231.js.map +0 -1
- package/dist/7053.js +0 -2
- package/dist/7053.js.map +0 -1
- /package/dist/{7053.js.LICENSE.txt → 8307.js.LICENSE.txt} +0 -0
- /package/dist/{5836.js.LICENSE.txt → 8555.js.LICENSE.txt} +0 -0
- /package/src/test-results/ui-elements/{resetFiltersEmptyState/index.scss → reset-filters-empty-state/filter-empty-state.scss} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
3
4
|
import { getDefaultsFromConfigSchema, useConfig, useLayoutType } from '@openmrs/esm-framework';
|
|
4
5
|
import { mockPatient } from 'tools';
|
|
5
6
|
import { mockResults } from '__mocks__';
|
|
6
7
|
import { type ConfigObject, configSchema } from '../../config-schema';
|
|
7
8
|
import { useGetManyObstreeData } from '../grouped-timeline';
|
|
8
|
-
import TreeView from './tree-view.component';
|
|
9
9
|
import { FilterProvider, type Roots } from '../filter/filter-context';
|
|
10
|
+
import { type ObsTreeNode } from '../grouped-timeline/useObstreeData';
|
|
11
|
+
import TreeView from './tree-view.component';
|
|
10
12
|
|
|
11
13
|
const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
|
|
12
14
|
const mockUseLayoutType = jest.mocked(useLayoutType);
|
|
@@ -93,7 +95,7 @@ describe('TreeView', () => {
|
|
|
93
95
|
|
|
94
96
|
it('renders the tree view when test data is successfully fetched', async () => {
|
|
95
97
|
mockUseGetManyObstreeData.mockReturnValue({
|
|
96
|
-
roots: mockResults
|
|
98
|
+
roots: mockResults as unknown as Array<ObsTreeNode>,
|
|
97
99
|
isLoading: false,
|
|
98
100
|
error: null,
|
|
99
101
|
});
|
|
@@ -105,4 +107,119 @@ describe('TreeView', () => {
|
|
|
105
107
|
expect(screen.getAllByText('Haemoglobin').length).toBeGreaterThan(0);
|
|
106
108
|
expect(screen.getAllByText('Hematocrit').length).toBeGreaterThan(0);
|
|
107
109
|
});
|
|
110
|
+
|
|
111
|
+
describe('Reset button - Tablet overlay', () => {
|
|
112
|
+
beforeEach(() => {
|
|
113
|
+
mockUseLayoutType.mockReturnValue('tablet');
|
|
114
|
+
mockUseGetManyObstreeData.mockReturnValue({
|
|
115
|
+
roots: mockResults as unknown as Array<ObsTreeNode>,
|
|
116
|
+
isLoading: false,
|
|
117
|
+
error: null,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should show reset tree button in tablet overlay when tree is opened', async () => {
|
|
122
|
+
const user = userEvent.setup();
|
|
123
|
+
|
|
124
|
+
renderTreeViewWithMockContext();
|
|
125
|
+
|
|
126
|
+
// Open the tree overlay by clicking the tree button
|
|
127
|
+
const treeButton = screen.getByRole('button', { name: /show tree/i });
|
|
128
|
+
await user.click(treeButton);
|
|
129
|
+
|
|
130
|
+
// Reset tree button should be visible in the overlay
|
|
131
|
+
const resetButton = screen.getByRole('button', { name: /reset tree/i });
|
|
132
|
+
expect(resetButton).toBeInTheDocument();
|
|
133
|
+
expect(resetButton).toBeEnabled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should show both reset and view buttons in overlay', async () => {
|
|
137
|
+
const user = userEvent.setup();
|
|
138
|
+
|
|
139
|
+
renderTreeViewWithMockContext();
|
|
140
|
+
|
|
141
|
+
// Open the tree overlay
|
|
142
|
+
const treeButton = screen.getByRole('button', { name: /show tree/i });
|
|
143
|
+
await user.click(treeButton);
|
|
144
|
+
|
|
145
|
+
// Both buttons should be visible and enabled
|
|
146
|
+
const resetButton = screen.getByRole('button', { name: /reset tree/i });
|
|
147
|
+
const viewButton = screen.getByRole('button', { name: /view.*results/i });
|
|
148
|
+
|
|
149
|
+
expect(resetButton).toBeInTheDocument();
|
|
150
|
+
expect(resetButton).toBeEnabled();
|
|
151
|
+
expect(viewButton).toBeInTheDocument();
|
|
152
|
+
expect(viewButton).toBeEnabled();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should reset filters when reset tree button is clicked', async () => {
|
|
156
|
+
const user = userEvent.setup();
|
|
157
|
+
|
|
158
|
+
renderTreeViewWithMockContext();
|
|
159
|
+
|
|
160
|
+
// Open the tree overlay
|
|
161
|
+
const treeButton = screen.getByRole('button', { name: /show tree/i });
|
|
162
|
+
await user.click(treeButton);
|
|
163
|
+
|
|
164
|
+
// Find an enabled checkbox (one with hasData: true in mock data)
|
|
165
|
+
// Mock data has "Platelets" with data
|
|
166
|
+
const plateletsCheckboxes = screen.getAllByRole('checkbox', { name: /platelets/i });
|
|
167
|
+
// Find the first enabled one
|
|
168
|
+
const plateletsCheckbox = plateletsCheckboxes.find((cb) => !cb.hasAttribute('disabled'));
|
|
169
|
+
|
|
170
|
+
expect(plateletsCheckbox).toBeDefined();
|
|
171
|
+
await user.click(plateletsCheckbox);
|
|
172
|
+
expect(plateletsCheckbox).toBeChecked();
|
|
173
|
+
|
|
174
|
+
// Click reset tree button
|
|
175
|
+
const resetButton = screen.getByRole('button', { name: /reset tree/i });
|
|
176
|
+
await user.click(resetButton);
|
|
177
|
+
|
|
178
|
+
// Checkbox should be unchecked
|
|
179
|
+
expect(plateletsCheckbox).not.toBeChecked();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should show filtered results count in view button', async () => {
|
|
183
|
+
const user = userEvent.setup();
|
|
184
|
+
|
|
185
|
+
renderTreeViewWithMockContext();
|
|
186
|
+
|
|
187
|
+
// Open the tree overlay
|
|
188
|
+
const treeButton = screen.getByRole('button', { name: /show tree/i });
|
|
189
|
+
await user.click(treeButton);
|
|
190
|
+
|
|
191
|
+
// Initially should show total count
|
|
192
|
+
const viewButton = screen.getByRole('button', { name: /view.*results/i });
|
|
193
|
+
expect(viewButton).toBeInTheDocument();
|
|
194
|
+
|
|
195
|
+
// Check a filter - find an enabled checkbox
|
|
196
|
+
const plateletsCheckboxes = screen.getAllByRole('checkbox', { name: /platelets/i });
|
|
197
|
+
const plateletsCheckbox = plateletsCheckboxes.find((cb) => !cb.hasAttribute('disabled'));
|
|
198
|
+
await user.click(plateletsCheckbox);
|
|
199
|
+
|
|
200
|
+
// View button should update with filtered count
|
|
201
|
+
expect(screen.getByRole('button', { name: /view.*results/i })).toBeInTheDocument();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should close overlay when view results button is clicked', async () => {
|
|
205
|
+
const user = userEvent.setup();
|
|
206
|
+
|
|
207
|
+
renderTreeViewWithMockContext();
|
|
208
|
+
|
|
209
|
+
// Open the tree overlay
|
|
210
|
+
const treeButton = screen.getByRole('button', { name: /show tree/i });
|
|
211
|
+
await user.click(treeButton);
|
|
212
|
+
|
|
213
|
+
// Reset tree button should be visible in the overlay
|
|
214
|
+
const resetButton = screen.getByRole('button', { name: /reset tree/i });
|
|
215
|
+
expect(resetButton).toBeInTheDocument();
|
|
216
|
+
|
|
217
|
+
// Click view results button
|
|
218
|
+
const viewButton = screen.getByRole('button', { name: /view.*results/i });
|
|
219
|
+
await user.click(viewButton);
|
|
220
|
+
|
|
221
|
+
// Overlay should be closed (reset button no longer visible)
|
|
222
|
+
expect(screen.queryByRole('button', { name: /reset tree/i })).not.toBeInTheDocument();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
108
225
|
});
|
|
@@ -3,6 +3,11 @@ import useSWR from 'swr';
|
|
|
3
3
|
import { type FetchResponse, openmrsFetch, showSnackbar, restBaseUrl } from '@openmrs/esm-framework';
|
|
4
4
|
import { assessValue } from '../loadPatientTestData/helpers';
|
|
5
5
|
import { type TreeNode } from '../filter/filter-types';
|
|
6
|
+
import {
|
|
7
|
+
selectReferenceRange,
|
|
8
|
+
formatReferenceRange,
|
|
9
|
+
type ReferenceRanges,
|
|
10
|
+
} from '../grouped-timeline/reference-range-helpers';
|
|
6
11
|
|
|
7
12
|
function computeTrendlineData(treeNode: TreeNode): Array<TreeNode> {
|
|
8
13
|
const tests: Array<TreeNode> = [];
|
|
@@ -11,12 +16,50 @@ function computeTrendlineData(treeNode: TreeNode): Array<TreeNode> {
|
|
|
11
16
|
}
|
|
12
17
|
treeNode?.subSets.forEach((subNode) => {
|
|
13
18
|
if ((subNode as TreeNode)?.obs) {
|
|
14
|
-
const
|
|
15
|
-
|
|
19
|
+
const subTreeNode = subNode as TreeNode;
|
|
20
|
+
// Node-level reference ranges for trendline (aggregate view)
|
|
21
|
+
const nodeRanges: ReferenceRanges = {
|
|
22
|
+
hiAbsolute: subTreeNode.hiAbsolute,
|
|
23
|
+
hiCritical: subTreeNode.hiCritical,
|
|
24
|
+
hiNormal: subTreeNode.hiNormal,
|
|
25
|
+
lowAbsolute: subTreeNode.lowAbsolute,
|
|
26
|
+
lowCritical: subTreeNode.lowCritical,
|
|
27
|
+
lowNormal: subTreeNode.lowNormal,
|
|
28
|
+
units: subTreeNode.units,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const range = formatReferenceRange(nodeRanges, subTreeNode.units);
|
|
32
|
+
|
|
33
|
+
const processedObs = subTreeNode.obs.map((ob) => {
|
|
34
|
+
// Note: Units are only at the concept/node level, not observation-level
|
|
35
|
+
const observationRanges: ReferenceRanges | undefined =
|
|
36
|
+
ob.lowNormal !== undefined || ob.hiNormal !== undefined
|
|
37
|
+
? {
|
|
38
|
+
hiAbsolute: ob.hiAbsolute,
|
|
39
|
+
hiCritical: ob.hiCritical,
|
|
40
|
+
hiNormal: ob.hiNormal,
|
|
41
|
+
lowAbsolute: ob.lowAbsolute,
|
|
42
|
+
lowCritical: ob.lowCritical,
|
|
43
|
+
lowNormal: ob.lowNormal,
|
|
44
|
+
}
|
|
45
|
+
: undefined;
|
|
46
|
+
|
|
47
|
+
const selectedRanges = selectReferenceRange(observationRanges, nodeRanges);
|
|
48
|
+
const assess = selectedRanges ? assessValue(selectedRanges) : assessValue(nodeRanges);
|
|
49
|
+
const interpretation = ob.interpretation ?? assess(ob.value);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
...ob,
|
|
53
|
+
interpretation,
|
|
54
|
+
lowNormal: ob.lowNormal,
|
|
55
|
+
hiNormal: ob.hiNormal,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
16
59
|
tests.push({
|
|
17
|
-
...
|
|
18
|
-
range
|
|
19
|
-
obs:
|
|
60
|
+
...subTreeNode,
|
|
61
|
+
range,
|
|
62
|
+
obs: processedObs,
|
|
20
63
|
});
|
|
21
64
|
} else if (subNode?.subSets) {
|
|
22
65
|
const subTreesTests = computeTrendlineData(subNode as TreeNode); // recursion
|
|
@@ -4,23 +4,43 @@ import { Button, InlineLoading, SkeletonText } from '@carbon/react';
|
|
|
4
4
|
import { LineChart, ScaleTypes, TickRotations } from '@carbon/charts-react';
|
|
5
5
|
import { ArrowLeftIcon, ConfigurableLink, formatDate } from '@openmrs/esm-framework';
|
|
6
6
|
import { EmptyState, type OBSERVATION_INTERPRETATION } from '@openmrs/esm-patient-common-lib';
|
|
7
|
-
import { useObstreeData } from './trendline-resource';
|
|
8
7
|
import { testResultsBasePath } from '../helpers';
|
|
8
|
+
import { useObstreeData } from './trendline-resource';
|
|
9
9
|
import CommonDataTable from '../overview/common-datatable.component';
|
|
10
10
|
import RangeSelector from './range-selector.component';
|
|
11
11
|
import styles from './trendline.scss';
|
|
12
12
|
|
|
13
13
|
interface TrendlineProps {
|
|
14
|
-
patientUuid: string;
|
|
15
|
-
conceptUuid: string;
|
|
16
14
|
basePath: string;
|
|
15
|
+
conceptUuid: string;
|
|
16
|
+
patientUuid: string;
|
|
17
17
|
hideTrendlineHeader?: boolean;
|
|
18
18
|
showBackToTimelineButton?: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
interface TrendLineBackgroundProps {
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const TrendLineBackground: React.FC<TrendLineBackgroundProps> = ({ ...props }) => (
|
|
26
|
+
<div {...props} className={styles.background} />
|
|
27
|
+
);
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
interface TrendlineHeaderProps {
|
|
30
|
+
isValidating: boolean;
|
|
31
|
+
patientUuid: string;
|
|
32
|
+
referenceRange: string;
|
|
33
|
+
showBackToTimelineButton: boolean;
|
|
34
|
+
title: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const TrendlineHeader: React.FC<TrendlineHeaderProps> = ({
|
|
38
|
+
patientUuid,
|
|
39
|
+
title,
|
|
40
|
+
referenceRange,
|
|
41
|
+
isValidating,
|
|
42
|
+
showBackToTimelineButton,
|
|
43
|
+
}) => {
|
|
24
44
|
const { t } = useTranslation();
|
|
25
45
|
return (
|
|
26
46
|
<div className={styles.header}>
|
|
@@ -28,9 +48,9 @@ const TrendlineHeader = ({ patientUuid, title, referenceRange, isValidating, sho
|
|
|
28
48
|
{showBackToTimelineButton && (
|
|
29
49
|
<ConfigurableLink to={testResultsBasePath(`/patient/${patientUuid}/chart`)}>
|
|
30
50
|
<Button
|
|
51
|
+
iconDescription={t('returnToTimeline', 'Return to timeline')}
|
|
31
52
|
kind="ghost"
|
|
32
53
|
renderIcon={(props: ComponentProps<typeof ArrowLeftIcon>) => <ArrowLeftIcon size={24} {...props} />}
|
|
33
|
-
iconDescription={t('returnToTimeline', 'Return to timeline')}
|
|
34
54
|
>
|
|
35
55
|
<span>{t('backToTimeline', 'Back to timeline')}</span>
|
|
36
56
|
</Button>
|
|
@@ -52,8 +72,8 @@ const Trendline: React.FC<TrendlineProps> = ({
|
|
|
52
72
|
hideTrendlineHeader = false,
|
|
53
73
|
showBackToTimelineButton = false,
|
|
54
74
|
}) => {
|
|
55
|
-
const { trendlineData, isLoading, isValidating } = useObstreeData(patientUuid, conceptUuid);
|
|
56
75
|
const { t } = useTranslation();
|
|
76
|
+
const { trendlineData, isLoading, isValidating } = useObstreeData(patientUuid, conceptUuid);
|
|
57
77
|
const { obs, display: chartTitle, hiNormal, lowNormal, units: leftAxisTitle, range: referenceRange } = trendlineData;
|
|
58
78
|
const bottomAxisTitle = t('date', 'Date');
|
|
59
79
|
const [range, setRange] = useState<[Date, Date]>();
|
|
@@ -83,52 +103,52 @@ const Trendline: React.FC<TrendlineProps> = ({
|
|
|
83
103
|
}
|
|
84
104
|
}, [obs]);
|
|
85
105
|
|
|
86
|
-
const data
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
const { data, tableData } = useMemo(() => {
|
|
107
|
+
const chartData: Array<{
|
|
108
|
+
date: Date;
|
|
109
|
+
value: number;
|
|
110
|
+
group: string;
|
|
111
|
+
min?: number;
|
|
112
|
+
max?: number;
|
|
113
|
+
}> = [];
|
|
93
114
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
};
|
|
103
|
-
}> = [];
|
|
115
|
+
const table: Array<{
|
|
116
|
+
id: string;
|
|
117
|
+
dateTime: string;
|
|
118
|
+
value: {
|
|
119
|
+
value: number;
|
|
120
|
+
interpretation: OBSERVATION_INTERPRETATION;
|
|
121
|
+
};
|
|
122
|
+
}> = [];
|
|
104
123
|
|
|
105
|
-
|
|
124
|
+
obs.forEach((observation, idx) => {
|
|
125
|
+
const normalRange =
|
|
126
|
+
hiNormal && lowNormal
|
|
127
|
+
? {
|
|
128
|
+
max: hiNormal,
|
|
129
|
+
min: lowNormal,
|
|
130
|
+
}
|
|
131
|
+
: {};
|
|
106
132
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
: {};
|
|
133
|
+
chartData.push({
|
|
134
|
+
date: new Date(Date.parse(observation.obsDatetime)),
|
|
135
|
+
value: parseFloat(observation.value),
|
|
136
|
+
group: chartTitle,
|
|
137
|
+
...normalRange,
|
|
138
|
+
});
|
|
115
139
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
140
|
+
table.push({
|
|
141
|
+
id: `${idx}`,
|
|
142
|
+
dateTime: observation.obsDatetime,
|
|
143
|
+
value: {
|
|
144
|
+
value: parseFloat(observation.value),
|
|
145
|
+
interpretation: observation.interpretation,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
121
148
|
});
|
|
122
149
|
|
|
123
|
-
tableData
|
|
124
|
-
|
|
125
|
-
dateTime: obs.obsDatetime,
|
|
126
|
-
value: {
|
|
127
|
-
value: parseFloat(obs.value),
|
|
128
|
-
interpretation: obs.interpretation,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
});
|
|
150
|
+
return { data: chartData, tableData: table };
|
|
151
|
+
}, [obs, chartTitle, hiNormal, lowNormal]);
|
|
132
152
|
|
|
133
153
|
const chartOptions = useMemo(
|
|
134
154
|
() => ({
|
|
@@ -143,7 +163,6 @@ const Trendline: React.FC<TrendlineProps> = ({
|
|
|
143
163
|
scaleType: ScaleTypes.TIME,
|
|
144
164
|
ticks: {
|
|
145
165
|
rotation: TickRotations.ALWAYS,
|
|
146
|
-
// formatter: x => x.toLocaleDateString("en-US", TableDateFormatOption)
|
|
147
166
|
},
|
|
148
167
|
domain: range,
|
|
149
168
|
},
|
|
@@ -239,11 +258,11 @@ const Trendline: React.FC<TrendlineProps> = ({
|
|
|
239
258
|
<div className={styles.container}>
|
|
240
259
|
{!hideTrendlineHeader && (
|
|
241
260
|
<TrendlineHeader
|
|
242
|
-
showBackToTimelineButton={showBackToTimelineButton}
|
|
243
261
|
isValidating={isValidating}
|
|
244
262
|
patientUuid={patientUuid}
|
|
245
|
-
title={dataset}
|
|
246
263
|
referenceRange={referenceRange}
|
|
264
|
+
showBackToTimelineButton={showBackToTimelineButton}
|
|
265
|
+
title={chartTitle}
|
|
247
266
|
/>
|
|
248
267
|
)}
|
|
249
268
|
<TrendLineBackground>
|
|
@@ -269,8 +288,25 @@ const Trendline: React.FC<TrendlineProps> = ({
|
|
|
269
288
|
);
|
|
270
289
|
};
|
|
271
290
|
|
|
272
|
-
|
|
273
|
-
|
|
291
|
+
interface DrawTableProps {
|
|
292
|
+
tableData: Array<{
|
|
293
|
+
id: string;
|
|
294
|
+
dateTime: string;
|
|
295
|
+
value: {
|
|
296
|
+
value: number;
|
|
297
|
+
interpretation: OBSERVATION_INTERPRETATION;
|
|
298
|
+
};
|
|
299
|
+
}>;
|
|
300
|
+
tableHeaderData: Array<{
|
|
301
|
+
header: string;
|
|
302
|
+
key: string;
|
|
303
|
+
}>;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const DrawTable = React.memo<DrawTableProps>(({ tableData, tableHeaderData }) => {
|
|
307
|
+
return <CommonDataTable data={tableData as any} tableHeaders={tableHeaderData} />;
|
|
274
308
|
});
|
|
275
309
|
|
|
310
|
+
DrawTable.displayName = 'DrawTable';
|
|
311
|
+
|
|
276
312
|
export default Trendline;
|
|
@@ -14,8 +14,8 @@ const FilterEmptyDataIllustration: React.FC<FilterEmptyDataIllustrationProps> =
|
|
|
14
14
|
<path
|
|
15
15
|
d="M57 57H1V1h56v56zM1 29h56M1 15h56M1 43h56M29 1v56M15 1v56M43 1v56"
|
|
16
16
|
stroke="#9ACBCA"
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
strokeWidth="1.44"
|
|
18
|
+
strokeLinejoin="round"
|
|
19
19
|
/>
|
|
20
20
|
</g>
|
|
21
21
|
</svg>
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Layer, Tile } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import type { EmptyStateProps } from '../../filter/filter-types';
|
|
4
5
|
import FilterEmptyDataIllustration from './filter-empty-data-illustration';
|
|
5
|
-
import styles from './
|
|
6
|
+
import styles from './filter-empty-state.scss';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
clearFilter(): void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const FilterEmptyState: React.FC<EmptyStateProps> = (props) => {
|
|
8
|
+
const FilterEmptyState: React.FC<EmptyStateProps> = (props) => {
|
|
12
9
|
const { t } = useTranslation();
|
|
13
10
|
|
|
14
11
|
return (
|
|
@@ -28,3 +25,5 @@ export const FilterEmptyState: React.FC<EmptyStateProps> = (props) => {
|
|
|
28
25
|
</Layer>
|
|
29
26
|
);
|
|
30
27
|
};
|
|
28
|
+
|
|
29
|
+
export default FilterEmptyState;
|
package/src/types.ts
CHANGED
|
@@ -67,6 +67,14 @@ export interface FHIRObservationResource {
|
|
|
67
67
|
hasMember?: Array<{
|
|
68
68
|
reference: string;
|
|
69
69
|
}>;
|
|
70
|
+
interpretation?: Array<{
|
|
71
|
+
coding: Array<{
|
|
72
|
+
code: string;
|
|
73
|
+
display: string;
|
|
74
|
+
system?: string;
|
|
75
|
+
}>;
|
|
76
|
+
text?: string;
|
|
77
|
+
}>;
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
export interface Concept {
|
|
@@ -107,7 +115,7 @@ export interface ConceptMeta {
|
|
|
107
115
|
range: string;
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
export interface ObsRecord extends FHIRObservationResource {
|
|
118
|
+
export interface ObsRecord extends Omit<FHIRObservationResource, 'interpretation'> {
|
|
111
119
|
conceptUuid: string;
|
|
112
120
|
relatedObs: Array<ObsRecord>;
|
|
113
121
|
meta: ConceptMeta;
|
|
@@ -142,6 +150,15 @@ export type Observation = {
|
|
|
142
150
|
obsDatetime: string;
|
|
143
151
|
value: string;
|
|
144
152
|
interpretation: OBSERVATION_INTERPRETATION;
|
|
153
|
+
// Observation-level reference ranges (criteria-based)
|
|
154
|
+
// Note: Units are only at the concept/node level (TestResult.units), not observation-level
|
|
155
|
+
hiAbsolute?: number;
|
|
156
|
+
hiCritical?: number;
|
|
157
|
+
hiNormal?: number;
|
|
158
|
+
lowAbsolute?: number;
|
|
159
|
+
lowCritical?: number;
|
|
160
|
+
lowNormal?: number;
|
|
161
|
+
range?: string; // Formatted range string for display
|
|
145
162
|
};
|
|
146
163
|
|
|
147
164
|
export type TestResult = {
|
|
@@ -168,6 +185,8 @@ export type MappedObservation = {
|
|
|
168
185
|
units: string;
|
|
169
186
|
lowCritical: number;
|
|
170
187
|
hiNormal: number;
|
|
188
|
+
hiAbsolute?: number;
|
|
189
|
+
hiCritical?: number;
|
|
171
190
|
flatName: string;
|
|
172
191
|
hasData: boolean;
|
|
173
192
|
range: string;
|
package/translations/am.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"add": "Add",
|
|
3
3
|
"additionalInstructions": "Additional instructions",
|
|
4
|
-
"addLabOrderWorkspaceTitle": "Add lab order",
|
|
5
4
|
"addOrderableForOrderType": "Add {{orderTypeDisplay}}",
|
|
6
5
|
"age": "Age",
|
|
7
6
|
"back": "Back",
|
|
8
|
-
"backToOrderBasket": "Back to order basket",
|
|
9
7
|
"backToTimeline": "Back to timeline",
|
|
10
8
|
"cancel": "Cancel",
|
|
11
9
|
"checkFilters": "Check the filters above",
|
|
@@ -25,10 +23,12 @@
|
|
|
25
23
|
"directlyAddToBasket": "Add to basket",
|
|
26
24
|
"discard": "Discard",
|
|
27
25
|
"editLabResults": "Edit laboratory tests",
|
|
26
|
+
"editOrderableForOrderType": "Edit {{orderTypeDisplay}}",
|
|
28
27
|
"editTestResults": "Edit test results",
|
|
29
28
|
"endDate": "End date",
|
|
30
29
|
"error": "Error",
|
|
31
30
|
"errorFetchingTestTypes": "Error fetching results for \"{{searchTerm}}\"",
|
|
31
|
+
"errorSavingLabOrder": "Error saving lab order",
|
|
32
32
|
"expandAll": "Expand all",
|
|
33
33
|
"female": "Female",
|
|
34
34
|
"gender": "Gender",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"results": "Results",
|
|
78
78
|
"Results": "Results",
|
|
79
79
|
"resultsText": "results",
|
|
80
|
-
"returnToOrderBasket": "Return to order basket",
|
|
81
80
|
"returnToTimeline": "Return to timeline",
|
|
82
81
|
"saveOrder": "Save order",
|
|
83
82
|
"scheduledDate": "Scheduled date",
|
|
@@ -99,7 +98,7 @@
|
|
|
99
98
|
"testName": "Test name",
|
|
100
99
|
"testResults": "test results",
|
|
101
100
|
"testResults_title": "Test Results",
|
|
102
|
-
"testResultsData": "
|
|
101
|
+
"testResultsData": "test results data",
|
|
103
102
|
"tests": "Tests",
|
|
104
103
|
"testType": "Test type",
|
|
105
104
|
"testTypeRequired": "Test type is required",
|
package/translations/ar.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"add": "Add",
|
|
3
3
|
"additionalInstructions": "تعليمات إضافية",
|
|
4
|
-
"addLabOrderWorkspaceTitle": "Add lab order",
|
|
5
4
|
"addOrderableForOrderType": "Add {{orderTypeDisplay}}",
|
|
6
5
|
"age": "Age",
|
|
7
6
|
"back": "Back",
|
|
8
|
-
"backToOrderBasket": "العودة إلى سلة الطلب",
|
|
9
7
|
"backToTimeline": "العودة إلى الجدول الزمني",
|
|
10
8
|
"cancel": "Cancel",
|
|
11
9
|
"checkFilters": "Check the filters above",
|
|
@@ -25,10 +23,12 @@
|
|
|
25
23
|
"directlyAddToBasket": "أضف إلى السلة",
|
|
26
24
|
"discard": "تجاهل",
|
|
27
25
|
"editLabResults": "Edit laboratory tests",
|
|
26
|
+
"editOrderableForOrderType": "Edit {{orderTypeDisplay}}",
|
|
28
27
|
"editTestResults": "Edit test results",
|
|
29
28
|
"endDate": "End date",
|
|
30
29
|
"error": "خطأ",
|
|
31
30
|
"errorFetchingTestTypes": "Error fetching results for \"{{searchTerm}}\"",
|
|
31
|
+
"errorSavingLabOrder": "Error saving lab order",
|
|
32
32
|
"expandAll": "Expand all",
|
|
33
33
|
"female": "Female",
|
|
34
34
|
"gender": "Gender",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"results": "نتائج",
|
|
78
78
|
"Results": "نتائج",
|
|
79
79
|
"resultsText": "نتائج",
|
|
80
|
-
"returnToOrderBasket": "Return to order basket",
|
|
81
80
|
"returnToTimeline": "العودة إلى الجدول الزمني",
|
|
82
81
|
"saveOrder": "حفظ الطلب",
|
|
83
82
|
"scheduledDate": "Scheduled date",
|
|
@@ -99,7 +98,7 @@
|
|
|
99
98
|
"testName": "اسم الاختبار",
|
|
100
99
|
"testResults": "نتائج الاختبار",
|
|
101
100
|
"testResults_title": "نتائج الاختبار",
|
|
102
|
-
"testResultsData": "
|
|
101
|
+
"testResultsData": "test results data",
|
|
103
102
|
"tests": "Tests",
|
|
104
103
|
"testType": "نوع الاختبار",
|
|
105
104
|
"testTypeRequired": "Test type is required",
|
package/translations/ar_SY.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"add": "Add",
|
|
3
3
|
"additionalInstructions": "Additional instructions",
|
|
4
|
-
"addLabOrderWorkspaceTitle": "Add lab order",
|
|
5
4
|
"addOrderableForOrderType": "Add {{orderTypeDisplay}}",
|
|
6
5
|
"age": "Age",
|
|
7
6
|
"back": "Back",
|
|
8
|
-
"backToOrderBasket": "Back to order basket",
|
|
9
7
|
"backToTimeline": "Back to timeline",
|
|
10
8
|
"cancel": "Cancel",
|
|
11
9
|
"checkFilters": "Check the filters above",
|
|
@@ -25,10 +23,12 @@
|
|
|
25
23
|
"directlyAddToBasket": "Add to basket",
|
|
26
24
|
"discard": "Discard",
|
|
27
25
|
"editLabResults": "Edit laboratory tests",
|
|
26
|
+
"editOrderableForOrderType": "Edit {{orderTypeDisplay}}",
|
|
28
27
|
"editTestResults": "Edit test results",
|
|
29
28
|
"endDate": "End date",
|
|
30
29
|
"error": "Error",
|
|
31
30
|
"errorFetchingTestTypes": "Error fetching results for \"{{searchTerm}}\"",
|
|
31
|
+
"errorSavingLabOrder": "Error saving lab order",
|
|
32
32
|
"expandAll": "Expand all",
|
|
33
33
|
"female": "Female",
|
|
34
34
|
"gender": "Gender",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"results": "Results",
|
|
78
78
|
"Results": "Results",
|
|
79
79
|
"resultsText": "results",
|
|
80
|
-
"returnToOrderBasket": "Return to order basket",
|
|
81
80
|
"returnToTimeline": "Return to timeline",
|
|
82
81
|
"saveOrder": "Save order",
|
|
83
82
|
"scheduledDate": "Scheduled date",
|
|
@@ -99,7 +98,7 @@
|
|
|
99
98
|
"testName": "Test name",
|
|
100
99
|
"testResults": "test results",
|
|
101
100
|
"testResults_title": "Test Results",
|
|
102
|
-
"testResultsData": "
|
|
101
|
+
"testResultsData": "test results data",
|
|
103
102
|
"tests": "Tests",
|
|
104
103
|
"testType": "Test type",
|
|
105
104
|
"testTypeRequired": "Test type is required",
|
package/translations/bn.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"add": "Add",
|
|
3
3
|
"additionalInstructions": "Additional instructions",
|
|
4
|
-
"addLabOrderWorkspaceTitle": "Add lab order",
|
|
5
4
|
"addOrderableForOrderType": "Add {{orderTypeDisplay}}",
|
|
6
5
|
"age": "Age",
|
|
7
6
|
"back": "Back",
|
|
8
|
-
"backToOrderBasket": "Back to order basket",
|
|
9
7
|
"backToTimeline": "Back to timeline",
|
|
10
8
|
"cancel": "Cancel",
|
|
11
9
|
"checkFilters": "Check the filters above",
|
|
@@ -25,10 +23,12 @@
|
|
|
25
23
|
"directlyAddToBasket": "Add to basket",
|
|
26
24
|
"discard": "Discard",
|
|
27
25
|
"editLabResults": "Edit laboratory tests",
|
|
26
|
+
"editOrderableForOrderType": "Edit {{orderTypeDisplay}}",
|
|
28
27
|
"editTestResults": "Edit test results",
|
|
29
28
|
"endDate": "End date",
|
|
30
29
|
"error": "Error",
|
|
31
30
|
"errorFetchingTestTypes": "Error fetching results for \"{{searchTerm}}\"",
|
|
31
|
+
"errorSavingLabOrder": "Error saving lab order",
|
|
32
32
|
"expandAll": "Expand all",
|
|
33
33
|
"female": "Female",
|
|
34
34
|
"gender": "Gender",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"results": "Results",
|
|
78
78
|
"Results": "Results",
|
|
79
79
|
"resultsText": "results",
|
|
80
|
-
"returnToOrderBasket": "Return to order basket",
|
|
81
80
|
"returnToTimeline": "Return to timeline",
|
|
82
81
|
"saveOrder": "Save order",
|
|
83
82
|
"scheduledDate": "Scheduled date",
|
|
@@ -99,7 +98,7 @@
|
|
|
99
98
|
"testName": "Test name",
|
|
100
99
|
"testResults": "test results",
|
|
101
100
|
"testResults_title": "Test Results",
|
|
102
|
-
"testResultsData": "
|
|
101
|
+
"testResultsData": "test results data",
|
|
103
102
|
"tests": "Tests",
|
|
104
103
|
"testType": "Test type",
|
|
105
104
|
"testTypeRequired": "Test type is required",
|