@kenyaemr/esm-active-visits-app 8.1.1-pre.129 → 8.1.2-pre.152

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 (74) hide show
  1. package/.turbo/turbo-build.log +17 -17
  2. package/dist/130.js +1 -1
  3. package/dist/130.js.map +1 -1
  4. package/dist/136.js +2 -0
  5. package/dist/136.js.map +1 -0
  6. package/dist/236.js +1 -0
  7. package/dist/240.js +1 -0
  8. package/dist/261.js +1 -0
  9. package/dist/271.js +1 -1
  10. package/dist/272.js +1 -0
  11. package/dist/319.js +1 -1
  12. package/dist/336.js +1 -0
  13. package/dist/378.js +1 -0
  14. package/dist/460.js +1 -1
  15. package/dist/539.js +1 -0
  16. package/dist/566.js +1 -0
  17. package/dist/6.js +1 -1
  18. package/dist/6.js.map +1 -1
  19. package/dist/652.js +1 -0
  20. package/dist/673.js +1 -0
  21. package/dist/705.js +1 -0
  22. package/dist/711.js +1 -0
  23. package/dist/725.js +1 -1
  24. package/dist/727.js +1 -0
  25. package/dist/737.js +1 -0
  26. package/dist/744.js +1 -0
  27. package/dist/899.js +1 -0
  28. package/dist/967.js +1 -1
  29. package/dist/kenyaemr-esm-active-visits-app.js +1 -1
  30. package/dist/kenyaemr-esm-active-visits-app.js.buildmanifest.json +389 -37
  31. package/dist/kenyaemr-esm-active-visits-app.js.map +1 -1
  32. package/dist/main.js +1 -1
  33. package/dist/main.js.map +1 -1
  34. package/dist/routes.json +1 -1
  35. package/package-lock.json +2028 -1668
  36. package/package.json +5 -5
  37. package/src/active-visits-widget/active-visits.component.tsx +156 -187
  38. package/src/active-visits-widget/active-visits.resource.tsx +202 -28
  39. package/src/active-visits-widget/active-visits.scss +18 -0
  40. package/src/active-visits-widget/active-visits.test.tsx +120 -83
  41. package/src/config-schema.ts +11 -1
  42. package/src/types/index.ts +156 -1
  43. package/src/visits-summary/visit-detail.component.tsx +3 -2
  44. package/src/visits-summary/visit-detail.test.tsx +27 -14
  45. package/src/visits-summary/visit.resource.ts +1 -135
  46. package/src/visits-summary/visits-components/encounter-list.component.tsx +9 -9
  47. package/src/visits-summary/visits-components/encounter-observations.component.tsx +1 -1
  48. package/src/visits-summary/visits-components/encounter-observations.test.tsx +1 -1
  49. package/src/visits-summary/visits-components/medications-summary.component.tsx +1 -1
  50. package/src/visits-summary/visits-components/notes-summary.component.tsx +1 -1
  51. package/src/visits-summary/visits-components/tests-summary.component.tsx +1 -1
  52. package/src/visits-summary/visits-components/visit-summary.component.tsx +4 -4
  53. package/translations/ar.json +6 -6
  54. package/translations/de.json +37 -0
  55. package/translations/es.json +11 -11
  56. package/translations/hi.json +37 -0
  57. package/translations/hi_IN.json +37 -0
  58. package/translations/id.json +37 -0
  59. package/translations/it.json +37 -0
  60. package/translations/ne.json +37 -0
  61. package/translations/pt.json +37 -0
  62. package/translations/pt_BR.json +37 -0
  63. package/translations/qu.json +37 -0
  64. package/translations/si.json +37 -0
  65. package/translations/sw.json +37 -0
  66. package/translations/sw_KE.json +37 -0
  67. package/translations/tr.json +37 -0
  68. package/translations/tr_TR.json +37 -0
  69. package/translations/uk.json +37 -0
  70. package/translations/vi.json +37 -0
  71. package/translations/zh.json +1 -1
  72. package/dist/586.js +0 -2
  73. package/dist/586.js.map +0 -1
  74. /package/dist/{586.js.LICENSE.txt → 136.js.LICENSE.txt} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-active-visits-app",
3
- "version": "8.1.1-pre.129",
3
+ "version": "8.1.2-pre.152",
4
4
  "description": "Active visits widget microfrontend for the OpenMRS SPA",
5
5
  "browser": "dist/kenyaemr-esm-active-visits-app.js",
6
6
  "main": "src/index.ts",
@@ -18,7 +18,7 @@
18
18
  "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
19
19
  "coverage": "yarn test --coverage",
20
20
  "typescript": "tsc",
21
- "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*modal.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
21
+ "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.resource.tsx' 'src/**/*.extension.tsx' 'src/**/*modal.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
22
22
  },
23
23
  "browserslist": [
24
24
  "extends browserslist-config-openmrs"
@@ -37,11 +37,11 @@
37
37
  "url": "https://github.com/openmrs/openmrs-esm-patient-management/issues"
38
38
  },
39
39
  "dependencies": {
40
- "@carbon/react": "~1.37.0",
40
+ "@carbon/react": "^1.71.0",
41
41
  "lodash-es": "^4.17.15"
42
42
  },
43
43
  "peerDependencies": {
44
- "@openmrs/esm-framework": "5.x",
44
+ "@openmrs/esm-framework": "6.x",
45
45
  "dayjs": "1.x",
46
46
  "react": "^18.1.0",
47
47
  "react-dom": "^18.1.0",
@@ -51,5 +51,5 @@
51
51
  "devDependencies": {
52
52
  "webpack": "^5.74.0"
53
53
  },
54
- "stableVersion": "8.1.0"
54
+ "stableVersion": "8.0.2"
55
55
  }
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useState, useCallback } from 'react';
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
2
  import {
3
3
  DataTable,
4
4
  DataTableSkeleton,
@@ -20,88 +20,53 @@ import {
20
20
  } from '@carbon/react';
21
21
  import { useTranslation } from 'react-i18next';
22
22
  import {
23
- useLayoutType,
23
+ ConfigurableLink,
24
+ ErrorState,
25
+ ExtensionSlot,
24
26
  isDesktop,
25
27
  useConfig,
28
+ useLayoutType,
26
29
  usePagination,
27
- ExtensionSlot,
28
- ErrorState,
29
- ConfigurableLink,
30
30
  } from '@openmrs/esm-framework';
31
31
  import { EmptyDataIllustration } from './empty-data-illustration.component';
32
- import { useActiveVisits } from './active-visits.resource';
32
+ import { useActiveVisits, useActiveVisitsSorting, useObsConcepts, useTableHeaders } from './active-visits.resource';
33
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
- }
34
+ import { type ActiveVisitsConfigSchema } from '../config-schema';
35
+ import { type ActiveVisit } from '../types';
95
36
 
96
37
  const ActiveVisitsTable = () => {
97
38
  const { t } = useTranslation();
98
- const config = useConfig();
39
+ const config = useConfig<ActiveVisitsConfigSchema>();
99
40
  const layout = useLayoutType();
100
41
  const pageSizes = config?.activeVisits?.pageSizes ?? [10, 20, 30, 40, 50];
101
42
  const [pageSize, setPageSize] = useState(config?.activeVisits?.pageSize ?? 10);
43
+ const { obsConcepts, isLoadingObsConcepts } = useObsConcepts(config.activeVisits.obs);
102
44
  const { activeVisits, isLoading, isValidating, error } = useActiveVisits();
103
45
  const [searchString, setSearchString] = useState('');
104
- const headerData = useMemo(() => generateTableHeaders(t, config), [config, t]);
46
+ const headerData = useTableHeaders(obsConcepts);
47
+
48
+ const transformVisitForDisplay = useCallback(
49
+ (visit: ActiveVisit) => {
50
+ const displayData = { ...visit };
51
+
52
+ // Add observation values to the display data
53
+ obsConcepts?.forEach((concept) => {
54
+ const obsValues = visit?.observations?.[concept.uuid] ?? [];
55
+ const latestObs = obsValues[0];
56
+
57
+ if (latestObs) {
58
+ // Handle both string and object values
59
+ displayData[`obs-${concept.uuid}`] =
60
+ typeof latestObs.value === 'object' ? latestObs.value.display : latestObs.value;
61
+ } else {
62
+ displayData[`obs-${concept.uuid}`] = '--';
63
+ }
64
+ });
65
+
66
+ return displayData;
67
+ },
68
+ [obsConcepts],
69
+ );
105
70
 
106
71
  const searchResults = useMemo(() => {
107
72
  if (activeVisits !== undefined && activeVisits.length > 0) {
@@ -118,10 +83,11 @@ const ActiveVisitsTable = () => {
118
83
  }
119
84
  }
120
85
 
121
- return activeVisits;
122
- }, [searchString, activeVisits]);
86
+ return activeVisits.map(transformVisitForDisplay);
87
+ }, [searchString, activeVisits, transformVisitForDisplay]);
123
88
 
124
- const { paginated, goTo, results, currentPage } = usePagination(searchResults, pageSize);
89
+ const { sortedRows, sortRow } = useActiveVisitsSorting(searchResults);
90
+ const { paginated, goTo, results, currentPage } = usePagination(sortedRows, pageSize as number);
125
91
 
126
92
  const handleSearch = useCallback(
127
93
  (e) => {
@@ -131,7 +97,7 @@ const ActiveVisitsTable = () => {
131
97
  [goTo, setSearchString],
132
98
  );
133
99
 
134
- if (isLoading) {
100
+ if (isLoading || isLoadingObsConcepts) {
135
101
  return (
136
102
  <div className={styles.activeVisitsContainer}>
137
103
  <div className={styles.activeVisitsDetailHeaderContainer}>
@@ -187,125 +153,128 @@ const ActiveVisitsTable = () => {
187
153
  </Layer>
188
154
  </div>
189
155
  );
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>
156
+ }
157
+
158
+ return (
159
+ <div className={styles.activeVisitsContainer}>
160
+ <div className={styles.activeVisitsDetailHeaderContainer}>
161
+ <div className={!isDesktop(layout) ? styles.tabletHeading : styles.desktopHeading}>
162
+ <h4>{t('activeVisits', 'Active Visits')}</h4>
200
163
  </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);
164
+ <div className={styles.backgroundDataFetchingIndicator}>
165
+ <span>{isValidating ? <InlineLoading /> : null}</span>
166
+ </div>
167
+ </div>
168
+ <Search
169
+ labelText=""
170
+ placeholder={t('filterTable', 'Filter table')}
171
+ onChange={handleSearch}
172
+ size={isDesktop(layout) ? 'sm' : 'lg'}
173
+ />
174
+ <DataTable
175
+ isSortable
176
+ useStaticWidth
177
+ rows={results}
178
+ headers={headerData}
179
+ sortRow={sortRow}
180
+ size={isDesktop(layout) ? 'sm' : 'lg'}
181
+ useZebraStyles={activeVisits?.length > 1}>
182
+ {({ rows, headers, getHeaderProps, getTableProps, getRowProps, getExpandHeaderProps }) => (
183
+ <TableContainer className={styles.tableContainer}>
184
+ <Table className={styles.activeVisitsTable} {...getTableProps()}>
185
+ <TableHead>
186
+ <TableRow>
187
+ <TableExpandHeader enableToggle {...getExpandHeaderProps()} />
188
+ {headers.map((header) => (
189
+ <TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader>
190
+ ))}
191
+ </TableRow>
192
+ </TableHead>
193
+ <TableBody>
194
+ {rows.map((row, index) => {
195
+ const currentVisit = activeVisits.find((visit) => visit.id === row.id);
226
196
 
227
- if (!currentVisit) {
228
- return null;
229
- }
197
+ if (!currentVisit) {
198
+ return null;
199
+ }
230
200
 
231
- const patientChartUrl = '${openmrsSpaBase}/patient/${patientUuid}/chart/Patient%20Summary';
201
+ const patientChartUrl = '${openmrsSpaBase}/patient/${patientUuid}/chart';
232
202
 
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>
203
+ return (
204
+ <React.Fragment key={`active-visit-row-${index}`}>
205
+ <TableExpandRow
206
+ {...getRowProps({ row })}
207
+ data-testid={`activeVisitRow${currentVisit.patientUuid || 'unknown'}`}>
208
+ {row.cells.map((cell) => (
209
+ <TableCell key={`active-visit-row-${index}-cell-${cell.id}`} data-testid={cell.id}>
210
+ {cell.info.header === 'name' && currentVisit.patientUuid ? (
211
+ <ConfigurableLink
212
+ to={patientChartUrl}
213
+ templateParams={{ patientUuid: currentVisit.patientUuid }}>
214
+ {cell.value}
215
+ </ConfigurableLink>
216
+ ) : (
217
+ cell.value
218
+ )}
219
+ </TableCell>
220
+ ))}
221
+ </TableExpandRow>
222
+ {row.isExpanded ? (
223
+ <TableRow className={styles.expandedActiveVisitRow}>
224
+ <th colSpan={headers.length + 2}>
225
+ <ExtensionSlot
226
+ className={styles.visitSummaryContainer}
227
+ name="visit-summary-slot"
228
+ state={{
229
+ patientUuid: currentVisit.patientUuid,
230
+ visitUuid: currentVisit.visitUuid,
231
+ }}
232
+ />
233
+ </th>
234
+ </TableRow>
235
+ ) : (
236
+ <TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
237
+ )}
238
+ </React.Fragment>
239
+ );
240
+ })}
241
+ </TableBody>
242
+ </Table>
243
+ </TableContainer>
285
244
  )}
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
- }
245
+ </DataTable>
246
+ {searchResults?.length === 0 && (
247
+ <div className={styles.filterEmptyState}>
248
+ <Layer level={0}>
249
+ <Tile className={styles.filterEmptyStateTile}>
250
+ <p className={styles.filterEmptyStateContent}>{t('noVisitsToDisplay', 'No visits to display')}</p>
251
+ <p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
252
+ </Tile>
253
+ </Layer>
254
+ </div>
255
+ )}
256
+ {paginated && (
257
+ <Pagination
258
+ forwardText="Next page"
259
+ backwardText="Previous page"
260
+ page={currentPage}
261
+ pageSize={pageSize}
262
+ pageSizes={pageSizes}
263
+ totalItems={searchResults?.length}
264
+ className={styles.pagination}
265
+ size={isDesktop(layout) ? 'sm' : 'lg'}
266
+ onChange={({ pageSize: newPageSize, page: newPage }) => {
267
+ if (newPageSize !== pageSize) {
268
+ setPageSize(newPageSize);
269
+ }
270
+ if (newPage !== currentPage) {
271
+ goTo(newPage);
272
+ }
273
+ }}
274
+ />
275
+ )}
276
+ </div>
277
+ );
309
278
  };
310
279
 
311
280
  export default ActiveVisitsTable;