@kenyaemr/esm-active-visits-app 7.0.2-pre.65

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 (79) hide show
  1. package/.turbo/turbo-build.log +32 -0
  2. package/dist/130.js +2 -0
  3. package/dist/130.js.LICENSE.txt +3 -0
  4. package/dist/130.js.map +1 -0
  5. package/dist/255.js +2 -0
  6. package/dist/255.js.LICENSE.txt +9 -0
  7. package/dist/255.js.map +1 -0
  8. package/dist/271.js +1 -0
  9. package/dist/316.js +2 -0
  10. package/dist/316.js.LICENSE.txt +19 -0
  11. package/dist/316.js.map +1 -0
  12. package/dist/319.js +1 -0
  13. package/dist/382.js +1 -0
  14. package/dist/382.js.map +1 -0
  15. package/dist/443.js +1 -0
  16. package/dist/443.js.map +1 -0
  17. package/dist/460.js +1 -0
  18. package/dist/574.js +1 -0
  19. package/dist/635.js +1 -0
  20. package/dist/635.js.map +1 -0
  21. package/dist/644.js +1 -0
  22. package/dist/729.js +1 -0
  23. package/dist/729.js.map +1 -0
  24. package/dist/757.js +1 -0
  25. package/dist/784.js +2 -0
  26. package/dist/784.js.LICENSE.txt +9 -0
  27. package/dist/784.js.map +1 -0
  28. package/dist/788.js +1 -0
  29. package/dist/807.js +1 -0
  30. package/dist/833.js +1 -0
  31. package/dist/835.js +1 -0
  32. package/dist/835.js.map +1 -0
  33. package/dist/875.js +2 -0
  34. package/dist/875.js.LICENSE.txt +15 -0
  35. package/dist/875.js.map +1 -0
  36. package/dist/879.js +1 -0
  37. package/dist/879.js.map +1 -0
  38. package/dist/kenyaemr-esm-active-visits-app.js +1 -0
  39. package/dist/kenyaemr-esm-active-visits-app.js.buildmanifest.json +580 -0
  40. package/dist/kenyaemr-esm-active-visits-app.js.map +1 -0
  41. package/dist/main.js +2 -0
  42. package/dist/main.js.LICENSE.txt +25 -0
  43. package/dist/main.js.map +1 -0
  44. package/dist/routes.json +1 -0
  45. package/jest.config.js +3 -0
  46. package/package.json +55 -0
  47. package/src/active-visits-widget/active-visits.component.tsx +311 -0
  48. package/src/active-visits-widget/active-visits.resource.tsx +148 -0
  49. package/src/active-visits-widget/active-visits.scss +191 -0
  50. package/src/active-visits-widget/active-visits.test.tsx +119 -0
  51. package/src/active-visits-widget/empty-data-illustration.component.tsx +39 -0
  52. package/src/config-schema.ts +57 -0
  53. package/src/declarations.d.ts +4 -0
  54. package/src/index.ts +21 -0
  55. package/src/root.scss +30 -0
  56. package/src/routes.json +20 -0
  57. package/src/types/index.ts +28 -0
  58. package/src/visits-summary/visit-detail-overview.scss +328 -0
  59. package/src/visits-summary/visit-detail.component.tsx +77 -0
  60. package/src/visits-summary/visit-detail.test.tsx +122 -0
  61. package/src/visits-summary/visit.resource.ts +190 -0
  62. package/src/visits-summary/visits-components/encounter-list.component.tsx +127 -0
  63. package/src/visits-summary/visits-components/encounter-observations.component.tsx +43 -0
  64. package/src/visits-summary/visits-components/encounter-observations.test.tsx +36 -0
  65. package/src/visits-summary/visits-components/medications-summary.component.tsx +105 -0
  66. package/src/visits-summary/visits-components/notes-summary.component.tsx +51 -0
  67. package/src/visits-summary/visits-components/tests-summary.component.tsx +21 -0
  68. package/src/visits-summary/visits-components/visit-summary.component.tsx +118 -0
  69. package/translations/am.json +35 -0
  70. package/translations/ar.json +35 -0
  71. package/translations/en.json +35 -0
  72. package/translations/es.json +35 -0
  73. package/translations/fr.json +35 -0
  74. package/translations/he.json +35 -0
  75. package/translations/km.json +35 -0
  76. package/translations/zh.json +35 -0
  77. package/translations/zh_CN.json +35 -0
  78. package/tsconfig.json +5 -0
  79. package/webpack.config.js +1 -0
@@ -0,0 +1,328 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+ @import '../root.scss';
5
+
6
+ .container {
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+ justify-content: flex-start;
11
+ }
12
+
13
+ .encounterHeading {
14
+ text-align: left;
15
+ width: 100%;
16
+ margin: 0 1rem 1.3125rem;
17
+ color: $ui-05;
18
+ }
19
+
20
+ .medicationRecord {
21
+ display: flex;
22
+ flex-direction: column;
23
+ justify-content: space-between;
24
+
25
+ .bodyLong01 {
26
+ margin: 0.25rem 0;
27
+ }
28
+ }
29
+
30
+ .medicationContainer {
31
+ background-color: $ui-01;
32
+ padding: 1rem;
33
+ width: 100% !important;
34
+ }
35
+
36
+ .dosage {
37
+ @include type.type-style('heading-compact-01');
38
+ }
39
+
40
+ .metadata {
41
+ @include type.type-style('label-01');
42
+ color: $text-02;
43
+ margin: spacing.$spacing-03 0 spacing.$spacing-05;
44
+ }
45
+
46
+ .visitsDetailWidgetContainer {
47
+ background-color: $ui-background;
48
+ width: 100%;
49
+ border: 1px solid $ui-03;
50
+ }
51
+
52
+ .visitsDetailHeaderContainer {
53
+ display: flex;
54
+ justify-content: space-between;
55
+ padding: spacing.$spacing-04 0 spacing.$spacing-04 spacing.$spacing-05;
56
+ background-color: $ui-background;
57
+ }
58
+
59
+ .visitsDetailHeaderContainer > h4:after {
60
+ content: '';
61
+ display: block;
62
+ width: 2rem;
63
+ padding-top: 0.188rem;
64
+ border-bottom: 0.375rem solid $brand-teal-01;
65
+ }
66
+
67
+ .customTable {
68
+ th {
69
+ padding: 0 !important;
70
+ }
71
+
72
+ tr[data-parent-row]:nth-child(odd) td {
73
+ background-color: $ui-02;
74
+ }
75
+
76
+ tbody tr[data-parent-row]:nth-child(even) td {
77
+ background-color: $ui-01;
78
+ }
79
+
80
+ td {
81
+ border-bottom: none !important;
82
+ }
83
+ }
84
+
85
+ .visitEmptyState {
86
+ text-align: center;
87
+ background-color: white;
88
+ padding: 2rem;
89
+ border: 1px solid $ui-03;
90
+ width: 100%;
91
+ }
92
+
93
+ .encounterEmptyState {
94
+ text-align: center;
95
+ margin: 0 1rem 1rem 1rem;
96
+ }
97
+
98
+ .expandedRow > td {
99
+ padding: inherit !important;
100
+ }
101
+
102
+ .expandedRow > td > div {
103
+ max-height: max-content !important;
104
+ }
105
+
106
+ .observation {
107
+ display: grid;
108
+ grid-template-columns: max-content auto;
109
+ grid-gap: 0.5rem;
110
+ margin: 0.5rem 0;
111
+ }
112
+
113
+ .observation > span {
114
+ align-self: center;
115
+ }
116
+
117
+ .summaryContainer {
118
+ background-color: $ui-background;
119
+ display: grid;
120
+ grid-template-columns: max-content auto;
121
+ padding: 1rem 0rem;
122
+ margin: 0 1rem;
123
+
124
+ :global(.cds--tabs) {
125
+ max-height: 7rem;
126
+ }
127
+ }
128
+
129
+ .flexSections {
130
+ display: flex;
131
+ }
132
+
133
+ .verticalTabs {
134
+ margin: 1rem 0;
135
+ scroll-behavior: smooth;
136
+
137
+ > ul {
138
+ flex-direction: column !important;
139
+ }
140
+
141
+ :global(.cds--tabs--scrollable .cds--tabs--scrollable__nav-item + .cds--tabs--scrollable__nav-item) {
142
+ margin-left: 0rem;
143
+ }
144
+
145
+ :global(.cds--tabs--scrollable .cds--tabs--scrollable__nav-link) {
146
+ border-bottom: 0 !important;
147
+ border-left: 2px solid $color-gray-30;
148
+ }
149
+ }
150
+
151
+ .tab {
152
+ outline: 0;
153
+ outline-offset: 0;
154
+ min-height: spacing.$spacing-07;
155
+
156
+ &:active,
157
+ &:focus {
158
+ outline: 2px solid var(--brand-03) !important;
159
+ }
160
+
161
+ &[aria-selected='true'] {
162
+ border-left: 3px solid var(--brand-03);
163
+ border-bottom: none;
164
+ font-weight: 600;
165
+ margin-left: 0rem !important;
166
+ }
167
+
168
+ &[aria-selected='false'] {
169
+ border-bottom: none;
170
+ border-left: 2px solid $ui-03;
171
+ margin-left: 0rem !important;
172
+ }
173
+ }
174
+
175
+ .tablist {
176
+ :global(.cds--tab--list) {
177
+ flex-direction: column;
178
+ max-height: fit-content;
179
+ overflow-x: visible;
180
+ }
181
+
182
+ > button :global(.cds--tabs .cds--tabs__nav-link) {
183
+ border-bottom: none;
184
+ }
185
+ }
186
+
187
+ .medicationBlock {
188
+ background-color: $ui-01;
189
+ padding: 0.625rem 6.75rem 0.75rem 1.063rem;
190
+ margin-top: 1.5rem;
191
+ width: 100% !important;
192
+ }
193
+
194
+ .medicationBlock:first-child {
195
+ margin-top: 0;
196
+ }
197
+
198
+ .diagnosisLabel {
199
+ @include type.type-style('heading-compact-01');
200
+ color: $text-02;
201
+ margin-top: 5px;
202
+ }
203
+
204
+ .diagnosesList {
205
+ display: flex;
206
+ flex-flow: row wrap;
207
+ padding-bottom: 0.5rem;
208
+ margin: 0 1rem;
209
+ border-bottom: 1px solid $ui-03;
210
+ }
211
+
212
+ .actions {
213
+ margin: 0 1rem;
214
+ }
215
+
216
+ .contentSwitcher {
217
+ // TODO: Remove once override gets added to styleguide
218
+ :global(.cds--content-switcher-btn) {
219
+ min-width: fit-content;
220
+ }
221
+
222
+ :global(.cds--content-switcher__label) {
223
+ height: spacing.$spacing-05;
224
+ }
225
+ }
226
+
227
+ .notesContainer {
228
+ margin-bottom: 2rem;
229
+ }
230
+
231
+ .noteText {
232
+ background-color: $ui-01;
233
+ padding: 1rem;
234
+ width: 100% !important;
235
+ white-space: pre-wrap;
236
+ }
237
+
238
+ .desktopHeading,
239
+ .tabletHeading {
240
+ text-align: left;
241
+ text-transform: capitalize;
242
+ margin-bottom: spacing.$spacing-05;
243
+
244
+ h4 {
245
+ @include type.type-style('heading-compact-02');
246
+ color: $text-02;
247
+
248
+ &:after {
249
+ content: '';
250
+ display: block;
251
+ width: 2rem;
252
+ padding-top: 3px;
253
+ border-bottom: 0.375rem solid;
254
+ @include brand-03(border-bottom-color);
255
+ }
256
+ }
257
+ }
258
+
259
+ .tile {
260
+ text-align: center;
261
+ }
262
+
263
+ .emptyStateContent {
264
+ @include type.type-style('heading-compact-01');
265
+ color: $text-02;
266
+ margin-top: spacing.$spacing-05;
267
+ margin-bottom: spacing.$spacing-03;
268
+ }
269
+
270
+ .emptyStateContainer {
271
+ background-color: $ui-02;
272
+ border: 1px solid $ui-03;
273
+ width: 100%;
274
+ margin: 0 auto;
275
+ max-width: 95vw;
276
+ padding-bottom: 0;
277
+ }
278
+
279
+ // Overriding styles for RTL support
280
+ html[dir='rtl'] {
281
+ .visitsDetailHeaderContainer {
282
+ padding: spacing.$spacing-04 spacing.$spacing-05 spacing.$spacing-04 0;
283
+ h4 {
284
+ text-align: right;
285
+ }
286
+ & > div {
287
+ & > div {
288
+ & :first-child {
289
+ border-bottom-left-radius: unset;
290
+ border-top-left-radius: unset;
291
+ border-bottom-right-radius: spacing.$spacing-02;
292
+ border-top-right-radius: spacing.$spacing-02;
293
+ }
294
+ & :first-child[aria-selected='false'] {
295
+ border-left: unset;
296
+ border-right: 0.0625rem solid #a6c8ff;
297
+ }
298
+ & :last-child {
299
+ border-bottom-right-radius: unset;
300
+ border-top-right-radius: unset;
301
+ border-bottom-left-radius: spacing.$spacing-02;
302
+ border-top-left-radius: spacing.$spacing-02;
303
+ }
304
+ & :last-child[aria-selected='false'] {
305
+ border-right: unset;
306
+ border-left: 0.0625rem solid #a6c8ff;
307
+ }
308
+ }
309
+ }
310
+ }
311
+ .summaryContainer {
312
+ .tablist {
313
+ & > div {
314
+ button {
315
+ text-align: right;
316
+ }
317
+ button[aria-selected='true'] {
318
+ border-left: unset;
319
+ border-right: 3px solid var(--brand-03);
320
+ }
321
+ button[aria-selected='false'] {
322
+ border-left: unset;
323
+ border-right: 2px solid #e0e0e0;
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }
@@ -0,0 +1,77 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ContentSwitcher, DataTableSkeleton, Switch } from '@carbon/react';
5
+ import { type Encounter, useVisit } from './visit.resource';
6
+ import { formatTime, formatDatetime, parseDate } from '@openmrs/esm-framework';
7
+ import EncounterList from './visits-components/encounter-list.component';
8
+ import VisitSummary from './visits-components/visit-summary.component';
9
+ import styles from './visit-detail-overview.scss';
10
+
11
+ interface VisitDetailComponentProps {
12
+ visitUuid: string;
13
+ patientUuid: string;
14
+ }
15
+
16
+ const VisitDetailComponent: React.FC<VisitDetailComponentProps> = ({ visitUuid, patientUuid }) => {
17
+ const { t } = useTranslation();
18
+ const [contentSwitcherIndex, setContentSwitcherIndex] = useState(0);
19
+ const { visit, isLoading } = useVisit(visitUuid);
20
+
21
+ const encounters = useMemo(
22
+ () =>
23
+ visit
24
+ ? visit?.encounters?.map((encounter: Encounter) => ({
25
+ id: encounter.uuid,
26
+ time: formatTime(parseDate(encounter.encounterDateTime)),
27
+ encounterType: encounter.encounterType.display,
28
+ provider: encounter.encounterProviders.length > 0 ? encounter.encounterProviders[0].display : '',
29
+ obs: encounter.obs,
30
+ }))
31
+ : [],
32
+ [visit],
33
+ );
34
+
35
+ if (isLoading) {
36
+ return <DataTableSkeleton role="progressbar" />;
37
+ }
38
+ if (visit) {
39
+ return (
40
+ <div className={styles.visitsDetailWidgetContainer}>
41
+ <div className={styles.visitsDetailHeaderContainer}>
42
+ <h4 className={styles.productiveHeading02}>
43
+ {visit?.visitType?.display}
44
+ <br />
45
+ <p className={classNames(styles.bodyLong01, styles.text02)}>
46
+ {formatDatetime(parseDate(visit?.startDatetime))}
47
+ </p>
48
+ </h4>
49
+ <div className={styles.actions}>
50
+ <ContentSwitcher
51
+ className={styles.contentSwitcher}
52
+ selectedIndex={contentSwitcherIndex}
53
+ onChange={({ index }) => setContentSwitcherIndex(index)}>
54
+ <Switch name="allEncounters" text={t('allEncounters', 'All Encounters')} />
55
+ <Switch name="visitSummary" text={t('visitSummary', 'Visit Summary')} />
56
+ </ContentSwitcher>
57
+ </div>
58
+ </div>
59
+ {contentSwitcherIndex === 0 && visit?.encounters && (
60
+ <EncounterList visitUuid={visit.uuid} encounters={encounters} />
61
+ )}
62
+ {contentSwitcherIndex === 1 && <VisitSummary encounters={visit.encounters} patientUuid={patientUuid} />}
63
+ </div>
64
+ );
65
+ } else {
66
+ return (
67
+ <div className={styles.visitEmptyState}>
68
+ <h4 className={styles.productiveHeading02}>{t('noEncountersFound', 'No encounters found')}</h4>
69
+ <p className={classNames(styles.bodyLong01, styles.text02)}>
70
+ {t('thereIsNoInformationToDisplayHere', 'There is no information to display here')}
71
+ </p>
72
+ </div>
73
+ );
74
+ }
75
+ };
76
+
77
+ export default VisitDetailComponent;
@@ -0,0 +1,122 @@
1
+ import React from 'react';
2
+ import { render, screen, act } from '@testing-library/react';
3
+ import VisitDetailComponent from './visit-detail.component';
4
+ import { useVisit } from './visit.resource';
5
+ import { formatDate } from '@openmrs/esm-framework';
6
+
7
+ jest.mock('./visit.resource');
8
+
9
+ const mockedUseVisit = useVisit as jest.Mock;
10
+
11
+ describe('VisitDetailComponent', () => {
12
+ const visitUuid = '497b8b17-54ec-4726-87ec-3c4da8cdcaeb';
13
+ const patientUuid = '691eed12-c0f1-11e2-94be-8c13b969e334';
14
+
15
+ it('renders loading indicator when data is loading', () => {
16
+ mockedUseVisit.mockReturnValueOnce({
17
+ visit: null,
18
+ isLoading: true,
19
+ });
20
+
21
+ render(<VisitDetailComponent visitUuid={visitUuid} patientUuid={patientUuid} />);
22
+
23
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
24
+ });
25
+
26
+ it('should render visit details and switches when data is available', () => {
27
+ let visitDate = new Date();
28
+ mockedUseVisit.mockReturnValueOnce({
29
+ visit: {
30
+ uuid: visitUuid,
31
+ visitType: { display: 'Some Visit Type' },
32
+ startDatetime: visitDate,
33
+ encounters: [],
34
+ },
35
+ isLoading: false,
36
+ });
37
+
38
+ render(<VisitDetailComponent visitUuid={visitUuid} patientUuid={patientUuid} />);
39
+
40
+ expect(screen.getByText(/Some Visit Type/)).toBeInTheDocument();
41
+ expect(screen.getByText(formatDate(visitDate), { collapseWhitespace: false })).toBeInTheDocument();
42
+
43
+ expect(screen.getByText('All Encounters')).toBeInTheDocument();
44
+ expect(screen.getByText('Visit Summary')).toBeInTheDocument();
45
+ });
46
+
47
+ it('should render EncounterLists when "All Encounters" switch is selected', () => {
48
+ mockedUseVisit.mockReturnValue({
49
+ visit: {
50
+ uuid: visitUuid,
51
+ visitType: { display: 'Some Visit Type' },
52
+ startDatetime: '2023-07-30T12:34:56Z',
53
+ encounters: [
54
+ {
55
+ uuid: 'encounter-1',
56
+ encounterDateTime: '2023-07-30T12:34:56Z',
57
+ encounterType: { display: 'Encounter Type' },
58
+ encounterProviders: [],
59
+ obs: [],
60
+ },
61
+ ],
62
+ },
63
+ isLoading: false,
64
+ });
65
+
66
+ render(<VisitDetailComponent visitUuid={visitUuid} patientUuid={patientUuid} />);
67
+
68
+ act(() => {
69
+ screen.getByText('All Encounters').click();
70
+ });
71
+
72
+ expect(screen.getByTestId('encountersTable')).toBeInTheDocument();
73
+ });
74
+
75
+ it('should render VisitSummaries when "Visit Summary" switch is selected', () => {
76
+ mockedUseVisit.mockReturnValue({
77
+ visit: {
78
+ uuid: visitUuid,
79
+ visitType: { display: 'Some Visit Type' },
80
+ startDatetime: '2023-07-30T12:34:56Z',
81
+ encounters: [
82
+ {
83
+ uuid: 'encounter-1',
84
+ encounterDateTime: '2023-07-30T12:34:56Z',
85
+ encounterType: { display: 'Encounter Type 1' },
86
+ encounterProviders: [],
87
+ obs: [],
88
+ orders: [],
89
+ },
90
+ {
91
+ uuid: 'encounter-2',
92
+ encounterDateTime: '2023-07-30T13:45:00Z',
93
+ encounterType: { display: 'Encounter Type 2' },
94
+ encounterProviders: [],
95
+ obs: [],
96
+ orders: [],
97
+ },
98
+ ],
99
+ },
100
+ isLoading: false,
101
+ });
102
+
103
+ render(<VisitDetailComponent visitUuid={visitUuid} patientUuid={patientUuid} />);
104
+
105
+ act(() => {
106
+ screen.getByText('Visit Summary').click();
107
+ });
108
+
109
+ expect(screen.getByRole('tablist', { name: 'Visit summary tabs' })).toBeInTheDocument();
110
+ });
111
+
112
+ it('should render loading indicator when data is loading', () => {
113
+ mockedUseVisit.mockReturnValue({
114
+ visit: null,
115
+ isLoading: false,
116
+ });
117
+
118
+ render(<VisitDetailComponent visitUuid={visitUuid} patientUuid={patientUuid} />);
119
+
120
+ expect(screen.queryByRole('button', { name: 'All Encounters' })).toBeNull();
121
+ });
122
+ });
@@ -0,0 +1,190 @@
1
+ import { openmrsFetch, restBaseUrl, type OpenmrsResource, type Visit } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+
4
+ export interface Encounter {
5
+ uuid: string;
6
+ encounterDateTime: string;
7
+ encounterProviders: Array<{
8
+ uuid: string;
9
+ display: string;
10
+ encounterRole: {
11
+ uuid: string;
12
+ display: string;
13
+ };
14
+ provider: {
15
+ uuid: string;
16
+ person: {
17
+ uuid: string;
18
+ display: string;
19
+ };
20
+ };
21
+ }>;
22
+ encounterType: {
23
+ uuid: string;
24
+ display: string;
25
+ };
26
+ obs: Array<Observation>;
27
+ orders: Array<Order>;
28
+ }
29
+
30
+ export interface EncounterProvider {
31
+ uuid: string;
32
+ display: string;
33
+ encounterRole: {
34
+ uuid: string;
35
+ display: string;
36
+ };
37
+ provider: {
38
+ uuid: string;
39
+ person: {
40
+ uuid: string;
41
+ display: string;
42
+ };
43
+ };
44
+ }
45
+
46
+ export interface Observation {
47
+ uuid: string;
48
+ concept: {
49
+ uuid: string;
50
+ display: string;
51
+ conceptClass: {
52
+ uuid: string;
53
+ display: string;
54
+ };
55
+ };
56
+ display: string;
57
+ groupMembers: null | Array<{
58
+ uuid: string;
59
+ concept: {
60
+ uuid: string;
61
+ display: string;
62
+ };
63
+ value: {
64
+ uuid: string;
65
+ display: string;
66
+ };
67
+ }>;
68
+ value: any;
69
+ obsDatetime: string;
70
+ }
71
+
72
+ export interface Order {
73
+ uuid: string;
74
+ dateActivated: string;
75
+ dateStopped?: Date | null;
76
+ dose: number;
77
+ dosingInstructions: string | null;
78
+ dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions';
79
+ doseUnits: {
80
+ uuid: string;
81
+ display: string;
82
+ };
83
+ drug: {
84
+ uuid: string;
85
+ name: string;
86
+ strength: string;
87
+ display: string;
88
+ };
89
+ duration: number;
90
+ durationUnits: {
91
+ uuid: string;
92
+ display: string;
93
+ };
94
+ frequency: {
95
+ uuid: string;
96
+ display: string;
97
+ };
98
+ numRefills: number;
99
+ orderNumber: string;
100
+ orderReason: string | null;
101
+ orderReasonNonCoded: string | null;
102
+ orderer: {
103
+ uuid: string;
104
+ person: {
105
+ uuid: string;
106
+ display: string;
107
+ };
108
+ };
109
+ orderType: {
110
+ uuid: string;
111
+ display: string;
112
+ };
113
+ route: {
114
+ uuid: string;
115
+ display: string;
116
+ };
117
+ quantity: number;
118
+ quantityUnits: OpenmrsResource;
119
+ }
120
+
121
+ export interface Note {
122
+ note: string;
123
+ provider: {
124
+ name: string;
125
+ role: string;
126
+ };
127
+ time: string;
128
+ }
129
+
130
+ export interface OrderItem {
131
+ order: Order;
132
+ provider: {
133
+ name: string;
134
+ role: string;
135
+ };
136
+ }
137
+
138
+ export function useVisit(visitUuid: string) {
139
+ const customRepresentation =
140
+ 'custom:(uuid,encounters:(uuid,encounterDatetime,' +
141
+ 'orders:(uuid,dateActivated,' +
142
+ 'drug:(uuid,name,strength),doseUnits:(uuid,display),' +
143
+ 'dose,route:(uuid,display),frequency:(uuid,display),' +
144
+ 'duration,durationUnits:(uuid,display),numRefills,' +
145
+ 'orderType:(uuid,display),orderer:(uuid,person:(uuid,display))),' +
146
+ 'obs:(uuid,concept:(uuid,display,conceptClass:(uuid,display)),' +
147
+ 'display,groupMembers:(uuid,concept:(uuid,display),' +
148
+ 'value:(uuid,display)),value),encounterType:(uuid,display),' +
149
+ 'encounterProviders:(uuid,display,encounterRole:(uuid,display),' +
150
+ 'provider:(uuid,person:(uuid,display)))),visitType:(uuid,name,display),startDatetime';
151
+
152
+ const apiUrl = `${restBaseUrl}/visit/${visitUuid}?v=${customRepresentation}`;
153
+
154
+ const { data, error, isLoading, isValidating } = useSWR<{ data: Visit }, Error>(
155
+ visitUuid ? apiUrl : null,
156
+ openmrsFetch,
157
+ );
158
+
159
+ return {
160
+ visit: data ? data.data : null,
161
+ isError: error,
162
+ isLoading,
163
+ isValidating,
164
+ };
165
+ }
166
+
167
+ export function getDosage(strength: string, doseNumber: number) {
168
+ if (!strength || !doseNumber) {
169
+ return '';
170
+ }
171
+
172
+ const i = strength.search(/\D/);
173
+ const strengthQuantity = parseInt(strength.substring(0, i));
174
+
175
+ const concentrationStartIndex = strength.search(/\//);
176
+
177
+ let strengthUnits = strength.substring(i);
178
+
179
+ if (concentrationStartIndex >= 0) {
180
+ strengthUnits = strength.substring(i, concentrationStartIndex);
181
+ const j = strength.substring(concentrationStartIndex + 1).search(/\D/);
182
+ const concentrationQuantity = parseInt(strength.substr(concentrationStartIndex + 1, j));
183
+ const concentrationUnits = strength.substring(concentrationStartIndex + 1 + j);
184
+ return `${doseNumber} ${strengthUnits} (${
185
+ (doseNumber / strengthQuantity) * concentrationQuantity
186
+ } ${concentrationUnits})`;
187
+ } else {
188
+ return `${strengthQuantity * doseNumber} ${strengthUnits}`;
189
+ }
190
+ }