@palladium-ethiopia/esm-clinical-workflow-app 5.4.2-pre.20 → 5.4.2-pre.26

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 (84) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/dist/152.js +1 -1
  3. package/dist/152.js.map +1 -1
  4. package/dist/164.js +1 -0
  5. package/dist/164.js.map +1 -0
  6. package/dist/208.js +1 -1
  7. package/dist/208.js.map +1 -1
  8. package/dist/209.js +1 -1
  9. package/dist/209.js.map +1 -1
  10. package/dist/363.js +1 -1
  11. package/dist/363.js.map +1 -1
  12. package/dist/534.js +1 -0
  13. package/dist/534.js.map +1 -0
  14. package/dist/677.js +1 -1
  15. package/dist/677.js.map +1 -1
  16. package/dist/689.js +1 -1
  17. package/dist/689.js.map +1 -1
  18. package/dist/712.js +1 -1
  19. package/dist/712.js.map +1 -1
  20. package/dist/771.js +1 -1
  21. package/dist/771.js.map +1 -1
  22. package/dist/825.js +1 -0
  23. package/dist/825.js.map +1 -0
  24. package/dist/914.js +37 -0
  25. package/dist/914.js.map +1 -0
  26. package/dist/926.js +17 -0
  27. package/dist/926.js.map +1 -0
  28. package/dist/ethiopia-esm-clinical-workflow-app.js +5 -5
  29. package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +144 -144
  30. package/dist/ethiopia-esm-clinical-workflow-app.js.map +1 -1
  31. package/dist/main.js +34 -8
  32. package/dist/main.js.map +1 -1
  33. package/dist/routes.json +1 -1
  34. package/package.json +1 -1
  35. package/src/config-schema.ts +98 -0
  36. package/src/index.ts +32 -1
  37. package/src/patient-chart/clinical-views/hooks/useEncountersByVisit.ts +13 -0
  38. package/src/patient-chart/constants.ts +11 -0
  39. package/src/patient-chart/visit/visit-history-table/diagnosis-tags.component.tsx +43 -0
  40. package/src/patient-chart/visit/visit-history-table/diagnosis-tags.module.scss +57 -0
  41. package/src/patient-chart/visit/visit-history-table/visit-actions-cell.component.tsx +20 -0
  42. package/src/patient-chart/visit/visit-history-table/visit-actions-cell.scss +4 -0
  43. package/src/patient-chart/visit/visit-history-table/visit-date-cell.component.tsx +19 -0
  44. package/src/patient-chart/visit/visit-history-table/visit-diagnoses-cell-with-certainty.component.tsx +31 -0
  45. package/src/patient-chart/visit/visit-history-table/visit-diagnoses-cell-with-certainty.module.scss +16 -0
  46. package/src/patient-chart/visit/visit-history-table/visit-history-table.component.tsx +144 -0
  47. package/src/patient-chart/visit/visit-history-table/visit-history-table.scss +25 -0
  48. package/src/patient-chart/visit/visit-history-table/visit-type-cell.component.tsx +15 -0
  49. package/src/patient-chart/visit/visits-widget/encounter-observations/encounter-observations.component.tsx +67 -0
  50. package/src/patient-chart/visit/visits-widget/encounter-observations/index.ts +3 -0
  51. package/src/patient-chart/visit/visits-widget/encounter-observations/styles.scss +22 -0
  52. package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/all-encounters-table.component.tsx +44 -0
  53. package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/encounters-table.component.tsx +388 -0
  54. package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/encounters-table.resource.ts +97 -0
  55. package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/encounters-table.scss +113 -0
  56. package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/visit-encounters-table.component.tsx +42 -0
  57. package/src/patient-chart/visit/visits-widget/past-visits-components/medications-summary.component.tsx +157 -0
  58. package/src/patient-chart/visit/visits-widget/past-visits-components/notes-summary.component.tsx +34 -0
  59. package/src/patient-chart/visit/visits-widget/past-visits-components/tests-summary.component.tsx +16 -0
  60. package/src/patient-chart/visit/visits-widget/past-visits-components/visit-actions-cell.scss +4 -0
  61. package/src/patient-chart/visit/visits-widget/past-visits-components/visit-summary.component.tsx +176 -0
  62. package/src/patient-chart/visit/visits-widget/past-visits-components/visit-summary.scss +72 -0
  63. package/src/patient-chart/visit/visits-widget/single-visit-details/visit-timeline/visit-timeline.component.tsx +94 -0
  64. package/src/patient-chart/visit/visits-widget/single-visit-details/visit-timeline/visit-timeline.scss +60 -0
  65. package/src/patient-chart/visit/visits-widget/visit-detail-overview.component.tsx +50 -0
  66. package/src/patient-chart/visit/visits-widget/visit-detail-overview.scss +262 -0
  67. package/src/patient-chart/visit/visits-widget/visit.resource.tsx +144 -0
  68. package/src/patient-notes/types/index.ts +194 -0
  69. package/src/patient-notes/visit-note-action-button.extension.tsx +28 -0
  70. package/src/patient-notes/visit-note-config-schema.ts +38 -0
  71. package/src/patient-notes/visit-notes-form-shadow.workspace.tsx +963 -0
  72. package/src/patient-notes/visit-notes-form.scss +453 -0
  73. package/src/patient-notes/visit-notes.resource.ts +113 -0
  74. package/src/routes.json +23 -0
  75. package/translations/am.json +168 -0
  76. package/translations/en.json +168 -0
  77. package/dist/410.js +0 -1
  78. package/dist/410.js.map +0 -1
  79. package/dist/484.js +0 -11
  80. package/dist/484.js.map +0 -1
  81. package/dist/540.js +0 -1
  82. package/dist/540.js.map +0 -1
  83. package/dist/545.js +0 -43
  84. package/dist/545.js.map +0 -1
@@ -0,0 +1,22 @@
1
+ @use '@carbon/layout';
2
+
3
+ .observation {
4
+ display: grid;
5
+ grid-template-columns: 1fr 1fr;
6
+ grid-gap: layout.$spacing-03;
7
+ margin-block: layout.$spacing-05;
8
+ margin-inline: 0 layout.$spacing-05;
9
+ }
10
+
11
+ .observation > span {
12
+ align-self: center;
13
+ justify-self: start;
14
+ }
15
+
16
+ .parentConcept {
17
+ font-weight: bold;
18
+ }
19
+
20
+ .childConcept {
21
+ padding-inline-start: layout.$spacing-04;
22
+ }
@@ -0,0 +1,44 @@
1
+ import React, { useState } from 'react';
2
+ import { type EncounterType } from '@openmrs/esm-framework';
3
+ import { type EncountersTableProps, usePaginatedEncounters } from './encounters-table.resource';
4
+ import EncountersTable from './encounters-table.component';
5
+
6
+ interface AllEncountersTableProps {
7
+ patientUuid: string;
8
+ }
9
+
10
+ /**
11
+ * This component shows a table of all encounters (across all visits) of a patient
12
+ */
13
+ const AllEncountersTable: React.FC<AllEncountersTableProps> = ({ patientUuid }) => {
14
+ const [encounterTypeToFilter, setEncounterTypeToFilter] = useState<EncounterType>(null);
15
+ const [pageSize, setPageSize] = useState(20);
16
+
17
+ const {
18
+ data: paginatedEncounters,
19
+ currentPage,
20
+ isLoading,
21
+ totalCount,
22
+ goTo,
23
+ mutate,
24
+ } = usePaginatedEncounters(patientUuid, encounterTypeToFilter?.uuid, pageSize);
25
+
26
+ const encountersTableProps: EncountersTableProps = {
27
+ currentPage,
28
+ encounterTypeToFilter,
29
+ goTo,
30
+ isLoading,
31
+ pageSize,
32
+ paginatedEncounters,
33
+ patientUuid,
34
+ setEncounterTypeToFilter,
35
+ setPageSize,
36
+ showEncounterTypeFilter: true,
37
+ showVisitType: true,
38
+ totalCount,
39
+ };
40
+
41
+ return <EncountersTable {...encountersTableProps} />;
42
+ };
43
+
44
+ export default AllEncountersTable;
@@ -0,0 +1,388 @@
1
+ import React, { type ComponentProps, useCallback, useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useSWRConfig } from 'swr';
4
+ import {
5
+ Button,
6
+ ComboBox,
7
+ DataTable,
8
+ DataTableSkeleton,
9
+ Layer,
10
+ OverflowMenu,
11
+ OverflowMenuItem,
12
+ Pagination,
13
+ Table,
14
+ TableBody,
15
+ TableCell,
16
+ TableContainer,
17
+ TableExpandedRow,
18
+ TableExpandHeader,
19
+ TableExpandRow,
20
+ TableHead,
21
+ TableHeader,
22
+ TableRow,
23
+ TableToolbar,
24
+ TableToolbarContent,
25
+ Tile,
26
+ } from '@carbon/react';
27
+ import {
28
+ EditIcon,
29
+ isDesktop,
30
+ launchWorkspace2,
31
+ showModal,
32
+ showSnackbar,
33
+ TrashCanIcon,
34
+ useConfig,
35
+ useLayoutType,
36
+ userHasAccess,
37
+ useSession,
38
+ type EncounterType,
39
+ ExtensionSlot,
40
+ useFeatureFlag,
41
+ } from '@openmrs/esm-framework';
42
+ import { invalidateVisitAndEncounterData, usePatientChartStore } from '@openmrs/esm-patient-common-lib';
43
+ import { jsonSchemaResourceName } from '../../../../constants';
44
+ import {
45
+ deleteEncounter,
46
+ mapEncounter,
47
+ useEncounterTypes,
48
+ type EncountersTableProps,
49
+ type MappedEncounter,
50
+ } from './encounters-table.resource';
51
+ import EncounterObservations from '../../encounter-observations';
52
+ import styles from './encounters-table.scss';
53
+ import { type ChartConfig } from '../../../../../config-schema';
54
+
55
+ /**
56
+ * This components is used by the AllEncountersTable and VisitEncountersTable to display
57
+ * a table of encounters, with the actual data, pagination and filtering logic passed in
58
+ * as props.
59
+ */
60
+ const EncountersTable: React.FC<EncountersTableProps> = ({
61
+ currentPage,
62
+ encounterTypeToFilter,
63
+ goTo,
64
+ isLoading,
65
+ pageSize,
66
+ paginatedEncounters,
67
+ patientUuid,
68
+ setEncounterTypeToFilter,
69
+ setPageSize,
70
+ showEncounterTypeFilter,
71
+ showVisitType,
72
+ totalCount,
73
+ }) => {
74
+ const { t } = useTranslation();
75
+ const pageSizes = [10, 20, 30, 40, 50];
76
+ const desktopLayout = isDesktop(useLayoutType());
77
+ const session = useSession();
78
+ const { mutateVisitContext } = usePatientChartStore(patientUuid);
79
+ const { mutate } = useSWRConfig();
80
+ const responsiveSize = desktopLayout ? 'sm' : 'lg';
81
+ const { data: encounterTypes, isLoading: isLoadingEncounterTypes } = useEncounterTypes();
82
+ const enableEmbeddedFormView = useFeatureFlag('enable-embedded-form-view');
83
+ const { encounterEditableDuration, encounterEditableDurationOverridePrivileges } = useConfig<ChartConfig>();
84
+ const paginatedMappedEncounters = useMemo(
85
+ () => (paginatedEncounters ?? []).map(mapEncounter).filter(Boolean),
86
+ [paginatedEncounters],
87
+ );
88
+
89
+ const tableHeaders = [
90
+ {
91
+ header: t('dateAndTime', 'Date & time'),
92
+ key: 'datetime',
93
+ },
94
+ ...(showVisitType
95
+ ? [
96
+ {
97
+ header: t('visitType', 'Visit type'),
98
+ key: 'visitType',
99
+ },
100
+ ]
101
+ : []),
102
+ {
103
+ header: t('encounterType', 'Encounter type'),
104
+ key: 'encounterType',
105
+ },
106
+ {
107
+ header: t('form', 'Form name'),
108
+ key: 'formName',
109
+ },
110
+ {
111
+ header: t('provider', 'Provider'),
112
+ key: 'provider',
113
+ },
114
+ ];
115
+
116
+ const handleDeleteEncounter = useCallback(
117
+ (encounterUuid: string, encounterTypeName?: string) => {
118
+ const dispose = showModal('delete-encounter-modal', {
119
+ close: () => dispose(),
120
+ encounterTypeName: encounterTypeName || '',
121
+ onConfirmation: () => {
122
+ const abortController = new AbortController();
123
+ deleteEncounter(encounterUuid, abortController)
124
+ .then(() => {
125
+ // Update current visit data for critical components
126
+ mutateVisitContext?.();
127
+
128
+ // Also invalidate visit history and encounter tables since the encounter was deleted
129
+ invalidateVisitAndEncounterData(mutate, patientUuid);
130
+
131
+ showSnackbar({
132
+ isLowContrast: true,
133
+ title: t('encounterDeleted', 'Encounter deleted'),
134
+ subtitle: t('encounterSuccessfullyDeleted', 'The encounter has been deleted successfully'),
135
+ kind: 'success',
136
+ });
137
+ })
138
+ .catch(() => {
139
+ showSnackbar({
140
+ isLowContrast: false,
141
+ title: t('error', 'Error'),
142
+ subtitle: t(
143
+ 'encounterWithError',
144
+ 'The encounter could not be deleted successfully. If the error persists, please contact your system administrator.',
145
+ ),
146
+ kind: 'error',
147
+ });
148
+ });
149
+ dispose();
150
+ },
151
+ });
152
+ },
153
+ [mutate, mutateVisitContext, patientUuid, t],
154
+ );
155
+
156
+ if (isLoadingEncounterTypes || isLoading) {
157
+ return <DataTableSkeleton role="progressbar" zebra />;
158
+ }
159
+
160
+ return (
161
+ <div className={styles.container}>
162
+ <DataTable
163
+ headers={tableHeaders}
164
+ overflowMenuOnHover={desktopLayout}
165
+ rows={paginatedMappedEncounters ?? []}
166
+ size={responsiveSize}
167
+ useZebraStyles={totalCount > 1 ? true : false}>
168
+ {({
169
+ rows,
170
+ headers,
171
+ getHeaderProps,
172
+ getRowProps,
173
+ getExpandHeaderProps,
174
+ getToolbarProps,
175
+ getTableProps,
176
+ }: {
177
+ headers: Array<{ header: React.ReactNode; key: string }>;
178
+ rows: Array<{ isExpanded: boolean; cells: Array<{ id: string; value: React.ReactNode }> }>;
179
+ [key: string]: any;
180
+ }) => (
181
+ <>
182
+ <TableContainer className={styles.tableContainer}>
183
+ {showEncounterTypeFilter && (
184
+ <TableToolbar {...getToolbarProps()}>
185
+ <TableToolbarContent>
186
+ <div className={styles.filterContainer}>
187
+ <ComboBox
188
+ aria-label={t('filterByEncounterType', 'Filter by encounter type')}
189
+ className={styles.substitutionType}
190
+ id="encounterTypeFilter"
191
+ items={encounterTypes}
192
+ itemToString={(item: EncounterType) => item?.display}
193
+ onChange={({ selectedItem }) => setEncounterTypeToFilter(selectedItem)}
194
+ placeholder={t('filterByEncounterType', 'Filter by encounter type')}
195
+ selectedItem={encounterTypeToFilter}
196
+ size={responsiveSize}
197
+ />
198
+ </div>
199
+ </TableToolbarContent>
200
+ </TableToolbar>
201
+ )}
202
+ <Table {...getTableProps()}>
203
+ <TableHead>
204
+ <TableRow>
205
+ <TableExpandHeader enableToggle {...getExpandHeaderProps()} />
206
+ {headers.map((header, i) => (
207
+ <TableHeader className={styles.tableHeader} key={i} {...getHeaderProps({ header })}>
208
+ {header.header}
209
+ </TableHeader>
210
+ ))}
211
+ <TableHeader aria-label={t('actions', 'Actions')} />
212
+ </TableRow>
213
+ </TableHead>
214
+ <TableBody>
215
+ {rows?.map((row, i) => {
216
+ const encounter = paginatedMappedEncounters[i];
217
+
218
+ if (!encounter) {
219
+ return null;
220
+ }
221
+
222
+ const isVisitNoteEncounter = (encounter: MappedEncounter) =>
223
+ encounter.encounterType === 'Visit Note' && !encounter.form;
224
+
225
+ const supportsEmbeddedFormView = (encounter: MappedEncounter) =>
226
+ encounter.form?.uuid &&
227
+ encounter.form.resources?.some((resource) => resource.name === jsonSchemaResourceName);
228
+
229
+ const encounterAgeInMinutes = (Date.now() - new Date(encounter.datetime).getTime()) / (1000 * 60);
230
+
231
+ const canDeleteEncounter =
232
+ userHasAccess(encounter.editPrivilege, session?.user) &&
233
+ (encounterEditableDuration === 0 ||
234
+ (encounterEditableDuration > 0 && encounterAgeInMinutes <= encounterEditableDuration) ||
235
+ encounterEditableDurationOverridePrivileges.some((privilege) =>
236
+ userHasAccess(privilege, session?.user),
237
+ ));
238
+
239
+ const canEditEncounter =
240
+ canDeleteEncounter && (encounter.form?.uuid || isVisitNoteEncounter(encounter));
241
+
242
+ return (
243
+ <React.Fragment key={encounter.id}>
244
+ <TableExpandRow {...getRowProps({ row })}>
245
+ {row.cells.map((cell) => (
246
+ <TableCell key={cell.id}>{cell.value}</TableCell>
247
+ ))}
248
+ <TableCell className="cds--table-column-menu">
249
+ <Layer className={styles.layer}>
250
+ {canDeleteEncounter && ( // equivalent to canDeleteEncounter || canEditEncounter
251
+ <OverflowMenu
252
+ aria-label={t('encounterTableActionsMenu', 'Encounter table actions menu')}
253
+ flipped
254
+ size={responsiveSize}
255
+ align="left">
256
+ {canEditEncounter && (
257
+ <OverflowMenuItem
258
+ className={styles.menuItem}
259
+ itemText={t('editThisEncounter', 'Edit this encounter')}
260
+ onClick={() => {
261
+ if (isVisitNoteEncounter(encounter)) {
262
+ launchWorkspace2('visit-notes-form-shadow-workspace', {
263
+ encounter,
264
+ formContext: 'editing',
265
+ patientUuid,
266
+ });
267
+ } else {
268
+ launchWorkspace2('patient-form-entry-workspace', {
269
+ form: encounter.form,
270
+ encounterUuid: encounter.id,
271
+ });
272
+ }
273
+ }}
274
+ />
275
+ )}
276
+ {canDeleteEncounter && (
277
+ <OverflowMenuItem
278
+ className={styles.menuItem}
279
+ hasDivider
280
+ isDelete
281
+ itemText={t('deleteThisEncounter', 'Delete this encounter')}
282
+ onClick={() => handleDeleteEncounter(encounter.id, encounter.form?.display)}
283
+ />
284
+ )}
285
+ </OverflowMenu>
286
+ )}
287
+ </Layer>
288
+ </TableCell>
289
+ </TableExpandRow>
290
+ {row.isExpanded ? (
291
+ <TableExpandedRow className={styles.expandedRow} colSpan={headers.length + 2}>
292
+ <>
293
+ {enableEmbeddedFormView && supportsEmbeddedFormView(encounter) ? (
294
+ <ExtensionSlot
295
+ name="form-widget-slot"
296
+ state={{
297
+ additionalProps: { mode: 'embedded-view' },
298
+ patientUuid: patientUuid,
299
+ formUuid: encounter.form.uuid,
300
+ encounterUuid: encounter.id,
301
+ promptBeforeClosing: () => {},
302
+ }}
303
+ />
304
+ ) : (
305
+ <EncounterObservations observations={encounter.obs} />
306
+ )}
307
+ <>
308
+ {canEditEncounter && (
309
+ <Button
310
+ kind="ghost"
311
+ onClick={() => {
312
+ if (isVisitNoteEncounter(encounter)) {
313
+ launchWorkspace2('visit-notes-form-shadow-workspace', {
314
+ encounter,
315
+ formContext: 'editing',
316
+ patientUuid,
317
+ });
318
+ } else {
319
+ launchWorkspace2('patient-form-entry-workspace', {
320
+ form: encounter.form,
321
+ encounterUuid: encounter.id,
322
+ });
323
+ }
324
+ }}
325
+ renderIcon={(props: ComponentProps<typeof EditIcon>) => (
326
+ <EditIcon size={16} {...props} />
327
+ )}>
328
+ {t('editThisEncounter', 'Edit this encounter')}
329
+ </Button>
330
+ )}
331
+ {canDeleteEncounter && (
332
+ <Button
333
+ kind="danger--ghost"
334
+ onClick={() => handleDeleteEncounter(encounter.id, encounter.form?.display)}
335
+ renderIcon={(props: ComponentProps<typeof TrashCanIcon>) => (
336
+ <TrashCanIcon size={16} {...props} />
337
+ )}>
338
+ {t('deleteThisEncounter', 'Delete this encounter')}
339
+ </Button>
340
+ )}
341
+ </>
342
+ </>
343
+ </TableExpandedRow>
344
+ ) : (
345
+ <TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
346
+ )}
347
+ </React.Fragment>
348
+ );
349
+ })}
350
+ </TableBody>
351
+ </Table>
352
+ {rows?.length === 0 && (
353
+ <div className={styles.tileContainer}>
354
+ <Tile className={styles.tile}>
355
+ <div className={styles.tileContent}>
356
+ <p className={styles.content}>{t('noEncountersToDisplay', 'No encounters to display')}</p>
357
+ <p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
358
+ </div>
359
+ </Tile>
360
+ </div>
361
+ )}
362
+ </TableContainer>
363
+ </>
364
+ )}
365
+ </DataTable>
366
+ {
367
+ <Pagination
368
+ forwardText={t('nextPage', 'Next page')}
369
+ backwardText={t('previousPage', 'Previous page')}
370
+ page={currentPage}
371
+ pageSize={pageSize}
372
+ pageSizes={pageSizes}
373
+ totalItems={totalCount}
374
+ onChange={({ pageSize: newPageSize, page }) => {
375
+ if (newPageSize !== pageSize) {
376
+ setPageSize(newPageSize);
377
+ }
378
+ if (page !== currentPage) {
379
+ goTo(page);
380
+ }
381
+ }}
382
+ />
383
+ }
384
+ </div>
385
+ );
386
+ };
387
+
388
+ export default EncountersTable;
@@ -0,0 +1,97 @@
1
+ import {
2
+ formatDatetime,
3
+ makeUrl,
4
+ openmrsFetch,
5
+ parseDate,
6
+ restBaseUrl,
7
+ type Diagnosis,
8
+ type Encounter,
9
+ type EncounterType,
10
+ type Obs,
11
+ useOpenmrsFetchAll,
12
+ useOpenmrsPagination,
13
+ } from '@openmrs/esm-framework';
14
+ import { type Form } from '@openmrs/esm-patient-common-lib';
15
+
16
+ export interface EncountersTableProps {
17
+ patientUuid: string;
18
+ totalCount: number;
19
+ currentPage: number;
20
+ goTo(page: number): void;
21
+ isLoading: boolean;
22
+ showVisitType: boolean;
23
+ paginatedEncounters: Array<Encounter>;
24
+ showEncounterTypeFilter: boolean;
25
+ encounterTypeToFilter?: EncounterType;
26
+ setEncounterTypeToFilter?: React.Dispatch<React.SetStateAction<EncounterType>>;
27
+ pageSize: number;
28
+ setPageSize: React.Dispatch<React.SetStateAction<number>>;
29
+ }
30
+
31
+ export interface MappedEncounter {
32
+ datetime: string;
33
+ diagnoses: Array<Diagnosis>;
34
+ editPrivilege: string;
35
+ encounterType: string;
36
+ form: Form;
37
+ formName: string;
38
+ id: string;
39
+ obs: Array<Obs>;
40
+ provider: string;
41
+ visitStartDatetime?: string;
42
+ visitStopDatetime?: string;
43
+ visitType: string;
44
+ visitTypeUuid?: string;
45
+ visitUuid: string;
46
+ }
47
+
48
+ export function deleteEncounter(encounterUuid: string, abortController: AbortController) {
49
+ return openmrsFetch(`${restBaseUrl}/encounter/${encounterUuid}`, {
50
+ method: 'DELETE',
51
+ signal: abortController.signal,
52
+ });
53
+ }
54
+
55
+ export function usePaginatedEncounters(patientUuid: string, encounterType: string, pageSize: number) {
56
+ const customRep = `custom:(uuid,display,diagnoses:(uuid,display,rank,diagnosis,certainty,voided),encounterDatetime,form:(uuid,display,name,description,encounterType,version,resources:(uuid,display,name,valueReference)),encounterType,visit,patient,obs:(uuid,concept:(uuid,display,conceptClass:(uuid,display)),display,groupMembers:(uuid,concept:(uuid,display),value:(uuid,display),display),value,obsDatetime),encounterProviders:(provider:(person)))`;
57
+ const url = new URL(makeUrl(`${restBaseUrl}/encounter`), window.location.toString());
58
+ url.searchParams.set('patient', patientUuid);
59
+ url.searchParams.set('v', customRep);
60
+ url.searchParams.set('order', 'desc');
61
+ encounterType && url.searchParams.set('encounterType', encounterType);
62
+ return useOpenmrsPagination<Encounter>(patientUuid ? url : null, pageSize);
63
+ }
64
+
65
+ export function useEncounterTypes() {
66
+ return useOpenmrsFetchAll<EncounterType>(`${restBaseUrl}/encountertype`, {
67
+ immutable: true,
68
+ });
69
+ }
70
+
71
+ export function mapEncounter(encounter: Encounter): MappedEncounter {
72
+ return {
73
+ id: encounter.uuid,
74
+ datetime: formatDatetime(parseDate(encounter.encounterDatetime), {
75
+ noToday: true,
76
+ }),
77
+ diagnoses:
78
+ encounter.diagnoses
79
+ ?.filter((diagnosis) => !diagnosis.voided)
80
+ .map((diagnosis) => ({
81
+ ...diagnosis,
82
+ certainty: diagnosis.certainty || 'PROVISIONAL',
83
+ })) || [],
84
+ encounterType: encounter.encounterType?.display,
85
+ editPrivilege: encounter.encounterType?.editPrivilege?.display,
86
+ form: encounter.form as Form,
87
+ formName: encounter.form?.display ?? '--',
88
+ obs: encounter.obs,
89
+ provider:
90
+ encounter.encounterProviders?.length > 0 ? encounter.encounterProviders[0].provider?.person?.display : '--',
91
+ visitStartDatetime: encounter.visit?.startDatetime,
92
+ visitStopDatetime: encounter.visit?.stopDatetime,
93
+ visitType: encounter.visit?.visitType?.display,
94
+ visitTypeUuid: encounter.visit?.visitType?.uuid,
95
+ visitUuid: encounter.visit?.uuid,
96
+ };
97
+ }
@@ -0,0 +1,113 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .container {
6
+ margin: layout.$spacing-05 0;
7
+ border: 1px solid $ui-03;
8
+ }
9
+
10
+ .tableContainer {
11
+ padding: 0;
12
+ border-bottom: none;
13
+
14
+ :global(.cds--data-table-header) {
15
+ padding: 0;
16
+ }
17
+
18
+ :global(.cds--table-toolbar) {
19
+ position: relative;
20
+ overflow: visible;
21
+ top: 0;
22
+ }
23
+
24
+ &:global(.cds--data-table-container) {
25
+ background: none !important;
26
+ }
27
+ }
28
+
29
+ .paginationContainer {
30
+ > div {
31
+ border: 1px solid $ui-03 !important;
32
+ border-top: none !important;
33
+ }
34
+ }
35
+
36
+ .filterContainer {
37
+ :global(.cds--dropdown__wrapper--inline) {
38
+ gap: 0;
39
+ }
40
+
41
+ :global(.cds--list-box__menu-icon) {
42
+ height: layout.$spacing-05;
43
+ }
44
+
45
+ label {
46
+ margin-right: layout.$spacing-05 !important;
47
+ }
48
+ }
49
+
50
+ .search {
51
+ max-width: 16rem;
52
+ }
53
+
54
+ .menuItem {
55
+ max-width: none;
56
+ }
57
+
58
+ .expandedRow {
59
+ padding-inline-start: 3.5rem;
60
+ padding-inline-end: 3.5rem;
61
+
62
+ > td {
63
+ padding: inherit !important;
64
+
65
+ > div {
66
+ max-height: max-content !important;
67
+ }
68
+ }
69
+
70
+ > div {
71
+ background-color: $ui-02;
72
+ }
73
+ }
74
+
75
+ .hiddenRow {
76
+ display: none;
77
+ }
78
+
79
+ .content {
80
+ @include type.type-style('heading-compact-02');
81
+ color: $text-02;
82
+ margin-bottom: layout.$spacing-03;
83
+ }
84
+
85
+ .tileContainer {
86
+ background-color: $ui-02;
87
+ border-top: 1px solid $ui-03;
88
+ padding: layout.$spacing-11 0;
89
+ }
90
+
91
+ .tile {
92
+ margin: auto;
93
+ width: fit-content;
94
+ }
95
+
96
+ .tileContent {
97
+ display: flex;
98
+ flex-direction: column;
99
+ align-items: center;
100
+ }
101
+
102
+ .helper {
103
+ @include type.type-style('body-compact-01');
104
+ color: $text-02;
105
+ }
106
+
107
+ .layer {
108
+ height: 100%;
109
+
110
+ :global(.cds--btn--primary) {
111
+ background-color: unset;
112
+ }
113
+ }