@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.
- package/.turbo/turbo-build.log +32 -0
- package/dist/130.js +2 -0
- package/dist/130.js.LICENSE.txt +3 -0
- package/dist/130.js.map +1 -0
- package/dist/255.js +2 -0
- package/dist/255.js.LICENSE.txt +9 -0
- package/dist/255.js.map +1 -0
- package/dist/271.js +1 -0
- package/dist/316.js +2 -0
- package/dist/316.js.LICENSE.txt +19 -0
- package/dist/316.js.map +1 -0
- package/dist/319.js +1 -0
- package/dist/382.js +1 -0
- package/dist/382.js.map +1 -0
- package/dist/443.js +1 -0
- package/dist/443.js.map +1 -0
- package/dist/460.js +1 -0
- package/dist/574.js +1 -0
- package/dist/635.js +1 -0
- package/dist/635.js.map +1 -0
- package/dist/644.js +1 -0
- package/dist/729.js +1 -0
- package/dist/729.js.map +1 -0
- package/dist/757.js +1 -0
- package/dist/784.js +2 -0
- package/dist/784.js.LICENSE.txt +9 -0
- package/dist/784.js.map +1 -0
- package/dist/788.js +1 -0
- package/dist/807.js +1 -0
- package/dist/833.js +1 -0
- package/dist/835.js +1 -0
- package/dist/835.js.map +1 -0
- package/dist/875.js +2 -0
- package/dist/875.js.LICENSE.txt +15 -0
- package/dist/875.js.map +1 -0
- package/dist/879.js +1 -0
- package/dist/879.js.map +1 -0
- package/dist/kenyaemr-esm-active-visits-app.js +1 -0
- package/dist/kenyaemr-esm-active-visits-app.js.buildmanifest.json +580 -0
- package/dist/kenyaemr-esm-active-visits-app.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +25 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +3 -0
- package/package.json +55 -0
- package/src/active-visits-widget/active-visits.component.tsx +311 -0
- package/src/active-visits-widget/active-visits.resource.tsx +148 -0
- package/src/active-visits-widget/active-visits.scss +191 -0
- package/src/active-visits-widget/active-visits.test.tsx +119 -0
- package/src/active-visits-widget/empty-data-illustration.component.tsx +39 -0
- package/src/config-schema.ts +57 -0
- package/src/declarations.d.ts +4 -0
- package/src/index.ts +21 -0
- package/src/root.scss +30 -0
- package/src/routes.json +20 -0
- package/src/types/index.ts +28 -0
- package/src/visits-summary/visit-detail-overview.scss +328 -0
- package/src/visits-summary/visit-detail.component.tsx +77 -0
- package/src/visits-summary/visit-detail.test.tsx +122 -0
- package/src/visits-summary/visit.resource.ts +190 -0
- package/src/visits-summary/visits-components/encounter-list.component.tsx +127 -0
- package/src/visits-summary/visits-components/encounter-observations.component.tsx +43 -0
- package/src/visits-summary/visits-components/encounter-observations.test.tsx +36 -0
- package/src/visits-summary/visits-components/medications-summary.component.tsx +105 -0
- package/src/visits-summary/visits-components/notes-summary.component.tsx +51 -0
- package/src/visits-summary/visits-components/tests-summary.component.tsx +21 -0
- package/src/visits-summary/visits-components/visit-summary.component.tsx +118 -0
- package/translations/am.json +35 -0
- package/translations/ar.json +35 -0
- package/translations/en.json +35 -0
- package/translations/es.json +35 -0
- package/translations/fr.json +35 -0
- package/translations/he.json +35 -0
- package/translations/km.json +35 -0
- package/translations/zh.json +35 -0
- package/translations/zh_CN.json +35 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
package/dist/routes.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.2.0"},"extensions":[{"name":"active-visits-widget","slot":"homepage-widgets-slot","component":"activeVisits","order":0},{"name":"visit-summary-widget","slot":"visit-summary-slot","component":"visitDetail"}],"pages":[],"version":"7.0.2-pre.65"}
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kenyaemr/esm-active-visits-app",
|
|
3
|
+
"version": "7.0.2-pre.65",
|
|
4
|
+
"description": "Active visits widget microfrontend for the OpenMRS SPA",
|
|
5
|
+
"browser": "dist/kenyaemr-esm-active-visits-app.js",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"source": true,
|
|
8
|
+
"license": "MPL-2.0",
|
|
9
|
+
"homepage": "https://github.com/openmrs/openmrs-esm-patient-management#readme",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "openmrs develop",
|
|
12
|
+
"serve": "webpack serve --mode=development",
|
|
13
|
+
"debug": "npm run serve",
|
|
14
|
+
"build": "webpack --mode production",
|
|
15
|
+
"analyze": "webpack --mode=production --env.analyze=true",
|
|
16
|
+
"lint": "cross-env eslint src --ext ts,tsx",
|
|
17
|
+
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color",
|
|
18
|
+
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
|
|
19
|
+
"coverage": "yarn test --coverage",
|
|
20
|
+
"typescript": "tsc",
|
|
21
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
|
|
22
|
+
},
|
|
23
|
+
"browserslist": [
|
|
24
|
+
"extends browserslist-config-openmrs"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"openmrs"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/openmrs/openmrs-esm-patient-management.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/openmrs/openmrs-esm-patient-management/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@carbon/react": "~1.37.0",
|
|
41
|
+
"lodash-es": "^4.17.15"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@openmrs/esm-framework": "5.x",
|
|
45
|
+
"dayjs": "1.x",
|
|
46
|
+
"react": "^18.1.0",
|
|
47
|
+
"react-dom": "^18.1.0",
|
|
48
|
+
"react-i18next": "11.x",
|
|
49
|
+
"swr": "2.x"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"webpack": "^5.74.0"
|
|
53
|
+
},
|
|
54
|
+
"stableVersion": "7.0.1"
|
|
55
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import React, { useMemo, useState, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
DataTable,
|
|
4
|
+
DataTableSkeleton,
|
|
5
|
+
InlineLoading,
|
|
6
|
+
Layer,
|
|
7
|
+
Pagination,
|
|
8
|
+
Search,
|
|
9
|
+
Table,
|
|
10
|
+
TableBody,
|
|
11
|
+
TableCell,
|
|
12
|
+
TableContainer,
|
|
13
|
+
TableExpandedRow,
|
|
14
|
+
TableExpandHeader,
|
|
15
|
+
TableExpandRow,
|
|
16
|
+
TableHead,
|
|
17
|
+
TableHeader,
|
|
18
|
+
TableRow,
|
|
19
|
+
Tile,
|
|
20
|
+
} from '@carbon/react';
|
|
21
|
+
import { useTranslation } from 'react-i18next';
|
|
22
|
+
import {
|
|
23
|
+
useLayoutType,
|
|
24
|
+
isDesktop,
|
|
25
|
+
useConfig,
|
|
26
|
+
usePagination,
|
|
27
|
+
ExtensionSlot,
|
|
28
|
+
ErrorState,
|
|
29
|
+
ConfigurableLink,
|
|
30
|
+
} from '@openmrs/esm-framework';
|
|
31
|
+
import { EmptyDataIllustration } from './empty-data-illustration.component';
|
|
32
|
+
import { useActiveVisits } from './active-visits.resource';
|
|
33
|
+
import styles from './active-visits.scss';
|
|
34
|
+
|
|
35
|
+
function generateTableHeaders(t, config) {
|
|
36
|
+
let headersIndex = 0;
|
|
37
|
+
|
|
38
|
+
const headers = [
|
|
39
|
+
{
|
|
40
|
+
id: headersIndex++,
|
|
41
|
+
header: t('visitStartTime', 'Visit Time'),
|
|
42
|
+
key: 'visitStartTime',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
config?.activeVisits?.identifiers?.map((identifier) => {
|
|
47
|
+
headers.push({
|
|
48
|
+
id: headersIndex++,
|
|
49
|
+
header: t(identifier?.header?.key, identifier?.header?.default),
|
|
50
|
+
key: identifier?.header?.key,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!config?.activeVisit?.identifiers) {
|
|
55
|
+
headers.push({
|
|
56
|
+
id: headersIndex++,
|
|
57
|
+
header: t('idNumber', 'ID Number'),
|
|
58
|
+
key: 'idNumber',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
config?.activeVisits?.attributes?.map((attribute) => {
|
|
63
|
+
headers.push({
|
|
64
|
+
id: headersIndex++,
|
|
65
|
+
header: t(attribute?.header?.key, attribute?.header?.default),
|
|
66
|
+
key: attribute?.header?.key,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
headers.push(
|
|
71
|
+
{
|
|
72
|
+
id: headersIndex++,
|
|
73
|
+
header: t('name', 'Name'),
|
|
74
|
+
key: 'name',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: headersIndex++,
|
|
78
|
+
header: t('gender', 'Gender'),
|
|
79
|
+
key: 'gender',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: headersIndex++,
|
|
83
|
+
header: t('age', 'Age'),
|
|
84
|
+
key: 'age',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: headersIndex++,
|
|
88
|
+
header: t('visitType', 'Visit Type'),
|
|
89
|
+
key: 'visitType',
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return headers;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ActiveVisitsTable = () => {
|
|
97
|
+
const { t } = useTranslation();
|
|
98
|
+
const config = useConfig();
|
|
99
|
+
const layout = useLayoutType();
|
|
100
|
+
const pageSizes = config?.activeVisits?.pageSizes ?? [10, 20, 30, 40, 50];
|
|
101
|
+
const [pageSize, setPageSize] = useState(config?.activeVisits?.pageSize ?? 10);
|
|
102
|
+
const { activeVisits, isLoading, isValidating, error } = useActiveVisits();
|
|
103
|
+
const [searchString, setSearchString] = useState('');
|
|
104
|
+
const headerData = useMemo(() => generateTableHeaders(t, config), [config, t]);
|
|
105
|
+
|
|
106
|
+
const searchResults = useMemo(() => {
|
|
107
|
+
if (activeVisits !== undefined && activeVisits.length > 0) {
|
|
108
|
+
if (searchString && searchString.trim() !== '') {
|
|
109
|
+
const search = searchString.toLowerCase();
|
|
110
|
+
return activeVisits?.filter((activeVisitRow) =>
|
|
111
|
+
Object.entries(activeVisitRow).some(([header, value]) => {
|
|
112
|
+
if (header === 'patientUuid') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return `${value}`.toLowerCase().includes(search);
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return activeVisits;
|
|
122
|
+
}, [searchString, activeVisits]);
|
|
123
|
+
|
|
124
|
+
const { paginated, goTo, results, currentPage } = usePagination(searchResults, pageSize);
|
|
125
|
+
|
|
126
|
+
const handleSearch = useCallback(
|
|
127
|
+
(e) => {
|
|
128
|
+
goTo(1);
|
|
129
|
+
setSearchString(e.target.value);
|
|
130
|
+
},
|
|
131
|
+
[goTo, setSearchString],
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (isLoading) {
|
|
135
|
+
return (
|
|
136
|
+
<div className={styles.activeVisitsContainer}>
|
|
137
|
+
<div className={styles.activeVisitsDetailHeaderContainer}>
|
|
138
|
+
<div className={!isDesktop(layout) ? styles.tabletHeading : styles.desktopHeading}>
|
|
139
|
+
<h4>{t('activeVisits', 'Active Visits')}</h4>
|
|
140
|
+
</div>
|
|
141
|
+
<div className={styles.backgroundDataFetchingIndicator}>
|
|
142
|
+
<span>{isValidating ? <InlineLoading /> : null}</span>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
<Search
|
|
146
|
+
labelText=""
|
|
147
|
+
placeholder={t('filterTable', 'Filter table')}
|
|
148
|
+
onChange={handleSearch}
|
|
149
|
+
size={isDesktop(layout) ? 'sm' : 'lg'}
|
|
150
|
+
disabled
|
|
151
|
+
/>
|
|
152
|
+
<DataTableSkeleton
|
|
153
|
+
rowCount={pageSize}
|
|
154
|
+
showHeader={false}
|
|
155
|
+
showToolbar={false}
|
|
156
|
+
zebra
|
|
157
|
+
columnCount={headerData?.length}
|
|
158
|
+
size={isDesktop(layout) ? 'sm' : 'lg'}
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (error) {
|
|
165
|
+
return (
|
|
166
|
+
<div className={styles.activeVisitsContainer}>
|
|
167
|
+
<Layer>
|
|
168
|
+
<ErrorState error={error} headerTitle={t('activeVisits', 'Active Visits')} />
|
|
169
|
+
</Layer>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!activeVisits.length) {
|
|
175
|
+
return (
|
|
176
|
+
<div className={styles.activeVisitsContainer}>
|
|
177
|
+
<Layer>
|
|
178
|
+
<Tile className={styles.tile}>
|
|
179
|
+
<div className={!isDesktop(layout) ? styles.tabletHeading : styles.desktopHeading}>
|
|
180
|
+
<h4>{t('activeVisits', 'Active Visits')}</h4>
|
|
181
|
+
</div>
|
|
182
|
+
<EmptyDataIllustration />
|
|
183
|
+
<p className={styles.content}>
|
|
184
|
+
{t('noActiveVisitsForLocation', 'There are no active visits to display for this location.')}
|
|
185
|
+
</p>
|
|
186
|
+
</Tile>
|
|
187
|
+
</Layer>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
} else {
|
|
191
|
+
return (
|
|
192
|
+
<div className={styles.activeVisitsContainer}>
|
|
193
|
+
<div className={styles.activeVisitsDetailHeaderContainer}>
|
|
194
|
+
<div className={!isDesktop(layout) ? styles.tabletHeading : styles.desktopHeading}>
|
|
195
|
+
<h4>{t('activeVisits', 'Active Visits')}</h4>
|
|
196
|
+
</div>
|
|
197
|
+
<div className={styles.backgroundDataFetchingIndicator}>
|
|
198
|
+
<span>{isValidating ? <InlineLoading /> : null}</span>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
<Search
|
|
202
|
+
labelText=""
|
|
203
|
+
placeholder={t('filterTable', 'Filter table')}
|
|
204
|
+
onChange={handleSearch}
|
|
205
|
+
size={isDesktop(layout) ? 'sm' : 'lg'}
|
|
206
|
+
/>
|
|
207
|
+
<DataTable
|
|
208
|
+
rows={results}
|
|
209
|
+
headers={headerData}
|
|
210
|
+
size={isDesktop(layout) ? 'sm' : 'lg'}
|
|
211
|
+
useZebraStyles={activeVisits?.length > 1 ? true : false}>
|
|
212
|
+
{({ rows, headers, getHeaderProps, getTableProps, getRowProps, getExpandHeaderProps }) => (
|
|
213
|
+
<TableContainer className={styles.tableContainer}>
|
|
214
|
+
<Table className={styles.activeVisitsTable} {...getTableProps()}>
|
|
215
|
+
<TableHead>
|
|
216
|
+
<TableRow>
|
|
217
|
+
<TableExpandHeader enableToggle {...getExpandHeaderProps()} />
|
|
218
|
+
{headers.map((header) => (
|
|
219
|
+
<TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader>
|
|
220
|
+
))}
|
|
221
|
+
</TableRow>
|
|
222
|
+
</TableHead>
|
|
223
|
+
<TableBody>
|
|
224
|
+
{rows.map((row, index) => {
|
|
225
|
+
const currentVisit = activeVisits.find((visit) => visit.id === row.id);
|
|
226
|
+
|
|
227
|
+
if (!currentVisit) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const patientChartUrl = '${openmrsSpaBase}/patient/${patientUuid}/chart/Patient%20Summary';
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<React.Fragment key={`active-visit-row-${index}`}>
|
|
235
|
+
<TableExpandRow
|
|
236
|
+
{...getRowProps({ row })}
|
|
237
|
+
data-testid={`activeVisitRow${currentVisit.patientUuid || 'unknown'}`}>
|
|
238
|
+
{row.cells.map((cell) => (
|
|
239
|
+
<TableCell key={`active-visit-row-${index}-cell-${cell.id}`} data-testid={cell.id}>
|
|
240
|
+
{cell.info.header === 'name' && currentVisit.patientUuid ? (
|
|
241
|
+
<ConfigurableLink
|
|
242
|
+
to={patientChartUrl}
|
|
243
|
+
templateParams={{ patientUuid: currentVisit.patientUuid }}>
|
|
244
|
+
{cell.value}
|
|
245
|
+
</ConfigurableLink>
|
|
246
|
+
) : (
|
|
247
|
+
cell.value
|
|
248
|
+
)}
|
|
249
|
+
</TableCell>
|
|
250
|
+
))}
|
|
251
|
+
</TableExpandRow>
|
|
252
|
+
{row.isExpanded ? (
|
|
253
|
+
<TableRow className={styles.expandedActiveVisitRow}>
|
|
254
|
+
<th colSpan={headers.length + 2}>
|
|
255
|
+
<ExtensionSlot
|
|
256
|
+
className={styles.visitSummaryContainer}
|
|
257
|
+
name="visit-summary-slot"
|
|
258
|
+
state={{
|
|
259
|
+
patientUuid: currentVisit.patientUuid,
|
|
260
|
+
visitUuid: currentVisit.visitUuid,
|
|
261
|
+
}}
|
|
262
|
+
/>
|
|
263
|
+
</th>
|
|
264
|
+
</TableRow>
|
|
265
|
+
) : (
|
|
266
|
+
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
|
|
267
|
+
)}
|
|
268
|
+
</React.Fragment>
|
|
269
|
+
);
|
|
270
|
+
})}
|
|
271
|
+
</TableBody>
|
|
272
|
+
</Table>
|
|
273
|
+
</TableContainer>
|
|
274
|
+
)}
|
|
275
|
+
</DataTable>
|
|
276
|
+
{searchResults?.length === 0 && (
|
|
277
|
+
<div className={styles.filterEmptyState}>
|
|
278
|
+
<Layer level={0}>
|
|
279
|
+
<Tile className={styles.filterEmptyStateTile}>
|
|
280
|
+
<p className={styles.filterEmptyStateContent}>{t('noVisitsToDisplay', 'No visits to display')}</p>
|
|
281
|
+
<p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
|
|
282
|
+
</Tile>
|
|
283
|
+
</Layer>
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
{paginated && (
|
|
287
|
+
<Pagination
|
|
288
|
+
forwardText="Next page"
|
|
289
|
+
backwardText="Previous page"
|
|
290
|
+
page={currentPage}
|
|
291
|
+
pageSize={pageSize}
|
|
292
|
+
pageSizes={pageSizes}
|
|
293
|
+
totalItems={searchResults?.length}
|
|
294
|
+
className={styles.pagination}
|
|
295
|
+
size={isDesktop(layout) ? 'sm' : 'lg'}
|
|
296
|
+
onChange={({ pageSize: newPageSize, page: newPage }) => {
|
|
297
|
+
if (newPageSize !== pageSize) {
|
|
298
|
+
setPageSize(newPageSize);
|
|
299
|
+
}
|
|
300
|
+
if (newPage !== currentPage) {
|
|
301
|
+
goTo(newPage);
|
|
302
|
+
}
|
|
303
|
+
}}
|
|
304
|
+
/>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export default ActiveVisitsTable;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import useSWRInfinite from 'swr/infinite';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import isToday from 'dayjs/plugin/isToday';
|
|
5
|
+
import last from 'lodash-es/last';
|
|
6
|
+
import {
|
|
7
|
+
openmrsFetch,
|
|
8
|
+
type Visit,
|
|
9
|
+
useSession,
|
|
10
|
+
type FetchResponse,
|
|
11
|
+
formatDatetime,
|
|
12
|
+
parseDate,
|
|
13
|
+
useConfig,
|
|
14
|
+
restBaseUrl,
|
|
15
|
+
} from '@openmrs/esm-framework';
|
|
16
|
+
dayjs.extend(isToday);
|
|
17
|
+
|
|
18
|
+
export interface ActiveVisit {
|
|
19
|
+
age: string;
|
|
20
|
+
id: string;
|
|
21
|
+
idNumber: string;
|
|
22
|
+
gender: string;
|
|
23
|
+
location: string;
|
|
24
|
+
name: string;
|
|
25
|
+
patientUuid: string;
|
|
26
|
+
visitStartTime: string;
|
|
27
|
+
visitType: string;
|
|
28
|
+
visitUuid: string;
|
|
29
|
+
[identifier: string]: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface VisitResponse {
|
|
33
|
+
results: Array<Visit>;
|
|
34
|
+
links: Array<{ rel: 'prev' | 'next' }>;
|
|
35
|
+
totalCount: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useActiveVisits() {
|
|
39
|
+
const session = useSession();
|
|
40
|
+
const config = useConfig();
|
|
41
|
+
const sessionLocation = session?.sessionLocation?.uuid;
|
|
42
|
+
|
|
43
|
+
const customRepresentation =
|
|
44
|
+
'custom:(uuid,patient:(uuid,identifiers:(identifier,uuid,identifierType:(name,uuid)),person:(age,display,gender,uuid,attributes:(value,attributeType:(uuid,display)))),' +
|
|
45
|
+
'visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime)';
|
|
46
|
+
|
|
47
|
+
const getUrl = (pageIndex, previousPageData: FetchResponse<VisitResponse>) => {
|
|
48
|
+
if (pageIndex && !previousPageData?.data?.links?.some((link) => link.rel === 'next')) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let url = `${restBaseUrl}/visit?v=${customRepresentation}&`;
|
|
53
|
+
let urlSearchParams = new URLSearchParams();
|
|
54
|
+
|
|
55
|
+
urlSearchParams.append('includeInactive', 'false');
|
|
56
|
+
urlSearchParams.append('totalCount', 'true');
|
|
57
|
+
urlSearchParams.append('location', `${sessionLocation}`);
|
|
58
|
+
|
|
59
|
+
if (pageIndex) {
|
|
60
|
+
urlSearchParams.append('startIndex', `${pageIndex * 50}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return url + urlSearchParams.toString();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const {
|
|
67
|
+
data,
|
|
68
|
+
error,
|
|
69
|
+
isLoading,
|
|
70
|
+
isValidating,
|
|
71
|
+
size: pageNumber,
|
|
72
|
+
setSize,
|
|
73
|
+
} = useSWRInfinite<FetchResponse<VisitResponse>, Error>(sessionLocation ? getUrl : null, openmrsFetch);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (data && data?.[pageNumber - 1]?.data?.links?.some((link) => link.rel === 'next')) {
|
|
77
|
+
setSize((currentSize) => currentSize + 1);
|
|
78
|
+
}
|
|
79
|
+
}, [data, pageNumber]);
|
|
80
|
+
|
|
81
|
+
const mapVisitProperties = (visit: Visit): ActiveVisit => {
|
|
82
|
+
// create base object
|
|
83
|
+
const activeVisits: ActiveVisit = {
|
|
84
|
+
age: visit?.patient?.person?.age,
|
|
85
|
+
id: visit.uuid,
|
|
86
|
+
idNumber: null,
|
|
87
|
+
gender: visit?.patient?.person?.gender,
|
|
88
|
+
location: visit?.location?.uuid,
|
|
89
|
+
name: visit?.patient?.person?.display,
|
|
90
|
+
patientUuid: visit?.patient?.uuid,
|
|
91
|
+
visitStartTime: formatDatetime(parseDate(visit?.startDatetime)),
|
|
92
|
+
visitType: visit?.visitType?.display,
|
|
93
|
+
visitUuid: visit.uuid,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// in case no configuration is given the previous behavior remains the same
|
|
97
|
+
if (!config?.activeVisits?.identifiers) {
|
|
98
|
+
activeVisits.idNumber = visit?.patient?.identifiers[0]?.identifier ?? '--';
|
|
99
|
+
} else {
|
|
100
|
+
// map identifiers on config
|
|
101
|
+
config?.activeVisits?.identifiers?.map((configIdentifier) => {
|
|
102
|
+
// check if in the current visit the patient has in his identifiers the current identifierType name
|
|
103
|
+
const visitIdentifier = visit?.patient?.identifiers.find(
|
|
104
|
+
(visitIdentifier) => visitIdentifier?.identifierType?.name === configIdentifier?.identifierName,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// add the new identifier or rewrite existing one to activeVisit object
|
|
108
|
+
// the parameter will corresponds to the name of the key value of the configuration
|
|
109
|
+
// and the respective value is the visit identifier
|
|
110
|
+
// If there isn't a identifier we display this default text '--'
|
|
111
|
+
activeVisits[configIdentifier.header?.key] = visitIdentifier?.identifier ?? '--';
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// map attributes on config
|
|
116
|
+
config?.activeVisits?.attributes?.map(({ display, header }) => {
|
|
117
|
+
// check if in the current visit the person has in his attributes the current display
|
|
118
|
+
const personAttributes = visit?.patient?.person?.attributes.find(
|
|
119
|
+
(personAttributes) => personAttributes?.attributeType?.display === display,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// add the new attribute or rewrite existing one to activeVisit object
|
|
123
|
+
// the parameter will correspond to the name of the key value of the configuration
|
|
124
|
+
// and the respective value is the persons value
|
|
125
|
+
// If there isn't a attribute we display this default text '--'
|
|
126
|
+
activeVisits[header?.key] = personAttributes?.value ?? '--';
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return activeVisits;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const formattedActiveVisits: Array<ActiveVisit> = data
|
|
133
|
+
? [].concat(...data?.map((res) => res?.data?.results?.map(mapVisitProperties)))
|
|
134
|
+
: [];
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
activeVisits: formattedActiveVisits,
|
|
138
|
+
error,
|
|
139
|
+
isLoading,
|
|
140
|
+
isValidating,
|
|
141
|
+
totalResults: data?.[0]?.data?.totalCount ?? 0,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const getOriginFromPathName = (pathname = '') => {
|
|
146
|
+
const from = pathname.split('/');
|
|
147
|
+
return last(from);
|
|
148
|
+
};
|