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

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 +5 -5
  2. package/dist/152.js +1 -1
  3. package/dist/152.js.map +1 -1
  4. package/dist/159.js +1 -1
  5. package/dist/159.js.map +1 -1
  6. package/dist/160.js +1 -0
  7. package/dist/160.js.map +1 -0
  8. package/dist/164.js +1 -1
  9. package/dist/164.js.map +1 -1
  10. package/dist/208.js +1 -1
  11. package/dist/208.js.map +1 -1
  12. package/dist/209.js +1 -1
  13. package/dist/209.js.map +1 -1
  14. package/dist/313.js +1 -0
  15. package/dist/313.js.map +1 -0
  16. package/dist/330.js +1 -0
  17. package/dist/330.js.map +1 -0
  18. package/dist/363.js +1 -1
  19. package/dist/363.js.map +1 -1
  20. package/dist/416.js +1 -0
  21. package/dist/416.js.map +1 -0
  22. package/dist/442.js +1 -1
  23. package/dist/442.js.map +1 -1
  24. package/dist/466.js +1 -1
  25. package/dist/466.js.map +1 -1
  26. package/dist/485.js +1 -0
  27. package/dist/485.js.map +1 -0
  28. package/dist/534.js +1 -1
  29. package/dist/534.js.map +1 -1
  30. package/dist/61.js +1 -1
  31. package/dist/61.js.map +1 -1
  32. package/dist/677.js +1 -1
  33. package/dist/677.js.map +1 -1
  34. package/dist/689.js +1 -1
  35. package/dist/689.js.map +1 -1
  36. package/dist/697.js +1 -1
  37. package/dist/697.js.map +1 -1
  38. package/dist/712.js +1 -1
  39. package/dist/712.js.map +1 -1
  40. package/dist/771.js +1 -1
  41. package/dist/771.js.map +1 -1
  42. package/dist/789.js +1 -1
  43. package/dist/789.js.map +1 -1
  44. package/dist/914.js +9 -9
  45. package/dist/914.js.map +1 -1
  46. package/dist/926.js +2 -2
  47. package/dist/926.js.map +1 -1
  48. package/dist/ethiopia-esm-clinical-workflow-app.js +5 -5
  49. package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +144 -48
  50. package/dist/ethiopia-esm-clinical-workflow-app.js.map +1 -1
  51. package/dist/main.js +13 -13
  52. package/dist/main.js.map +1 -1
  53. package/dist/routes.json +1 -1
  54. package/package.json +1 -1
  55. package/src/config-schema.ts +21 -0
  56. package/src/index.ts +23 -0
  57. package/src/patient-chart/visit/visit-history-table/diagnosis-tags.module.scss +1 -1
  58. package/src/patient-notes/visit-notes-form.scss +8 -8
  59. package/src/patient-transfer/confirm-transfer-dialog.modal.tsx +58 -0
  60. package/src/patient-transfer/patient-transfer-action-button.extension.tsx +126 -0
  61. package/src/patient-transfer/queue-table-transfer-column.component.tsx +40 -0
  62. package/src/patient-transfer/transfer-data.resource.ts +66 -0
  63. package/src/patient-transfer/transfer-details.modal.tsx +42 -0
  64. package/src/patient-transfer/transfer-details.scss +18 -0
  65. package/src/patient-transfer/transfer-notes-overview.extension.tsx +63 -0
  66. package/src/patient-transfer/transfer-notes-overview.scss +88 -0
  67. package/src/patient-transfer/transfer-notes-table.component.tsx +205 -0
  68. package/src/patient-transfer/transfer-notes.resource.ts +93 -0
  69. package/src/routes.json +37 -0
  70. package/src/types/index.ts +15 -0
  71. package/translations/am.json +8 -0
  72. package/translations/en.json +8 -0
  73. package/dist/825.js +0 -1
  74. package/dist/825.js.map +0 -1
@@ -0,0 +1,205 @@
1
+ import React, { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import classNames from 'classnames';
4
+ import {
5
+ DataTable,
6
+ type DataTableCell,
7
+ type DataTableSortState,
8
+ Table,
9
+ TableCell,
10
+ TableContainer,
11
+ TableBody,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ TableExpandHeader,
16
+ TableExpandRow,
17
+ TableExpandedRow,
18
+ } from '@carbon/react';
19
+ import orderBy from 'lodash-es/orderBy';
20
+ import { formatDate, formatTime, parseDate, useLayoutType, usePagination } from '@openmrs/esm-framework';
21
+ import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
22
+ import type { TransferNote } from './transfer-notes.resource';
23
+ import styles from './transfer-notes-overview.scss';
24
+
25
+ interface TransferNotesTableProps {
26
+ notes: Array<TransferNote>;
27
+ pageSize: number;
28
+ pageUrl: string;
29
+ urlLabel: string;
30
+ }
31
+
32
+ const TransferNotesTable: React.FC<TransferNotesTableProps> = ({ notes, pageSize, pageUrl, urlLabel }) => {
33
+ const { t } = useTranslation();
34
+ const layout = useLayoutType();
35
+ const isTablet = layout === 'tablet';
36
+
37
+ const [sortParams, setSortParams] = useState({ key: '', order: 'none' });
38
+
39
+ const tableHeaders = [
40
+ {
41
+ key: 'encounterDate',
42
+ header: t('date', 'Date'),
43
+ },
44
+ {
45
+ key: 'fromLocation',
46
+ header: t('fromLocation', 'From Location'),
47
+ },
48
+ ];
49
+
50
+ const sortDate = (myArray: Array<TransferNote>, order: string) => {
51
+ const sortOrder: 'asc' | 'desc' = order === 'DESC' ? 'desc' : 'asc';
52
+ return orderBy(myArray, [(note: TransferNote) => new Date(parseDate(note.encounterDate)).getTime()], [sortOrder]);
53
+ };
54
+
55
+ const sortString = (myArray: Array<TransferNote>, order: string, key: string) => {
56
+ const sortOrder: 'asc' | 'desc' = order === 'DESC' ? 'desc' : 'asc';
57
+ return orderBy(myArray, [(note: TransferNote) => (note as any)[key]?.toLowerCase() || ''], [sortOrder]);
58
+ };
59
+
60
+ const sortRows = (
61
+ cellA: any,
62
+ cellB: any,
63
+ {
64
+ sortDirection,
65
+ sortStates,
66
+ key,
67
+ }: {
68
+ sortDirection: string;
69
+ sortStates: any;
70
+ key: string;
71
+ locale: string;
72
+ },
73
+ ) => {
74
+ const sortKey = Object.keys(sortStates).find((k) => sortStates[k] === sortDirection);
75
+ setSortParams({ key: sortKey ?? key, order: sortDirection });
76
+ return 0;
77
+ };
78
+
79
+ const sortData = (data: Array<TransferNote>): Array<TransferNote> => {
80
+ if (!sortParams.key || sortParams.order === 'none') {
81
+ return data;
82
+ }
83
+
84
+ const { key, order } = sortParams;
85
+ let sortedData = [];
86
+
87
+ if (key === 'encounterDate') {
88
+ sortedData = sortDate(data, order);
89
+ } else if (key === 'fromLocation') {
90
+ sortedData = sortString(data, order, 'fromLocation');
91
+ } else {
92
+ sortedData = data;
93
+ }
94
+
95
+ return sortedData;
96
+ };
97
+
98
+ const { results: paginatedNotes, goTo, currentPage } = usePagination(sortData(notes), pageSize);
99
+
100
+ const tableRows = React.useMemo(() => {
101
+ return paginatedNotes.map((note) => ({
102
+ ...note,
103
+ id: note.id,
104
+ encounterDate: formatDate(new Date(note.encounterDate), { mode: 'wide' }),
105
+ fromLocation: note.fromLocation,
106
+ observations: note.observations, // Include all observations for dynamic display
107
+ }));
108
+ }, [paginatedNotes]);
109
+
110
+ return (
111
+ <div>
112
+ <DataTable
113
+ rows={tableRows}
114
+ headers={tableHeaders}
115
+ isSortable
116
+ size={isTablet ? 'lg' : 'sm'}
117
+ useZebraStyles
118
+ sortRow={sortRows}>
119
+ {({
120
+ getExpandedRowProps,
121
+ getExpandHeaderProps,
122
+ getHeaderProps,
123
+ getRowProps,
124
+ getTableContainerProps,
125
+ getTableProps,
126
+ headers,
127
+ rows,
128
+ }) => (
129
+ <TableContainer className={styles.tableContainer} {...getTableContainerProps()}>
130
+ <Table {...getTableProps()}>
131
+ <TableHead>
132
+ <TableRow>
133
+ <TableExpandHeader enableToggle {...getExpandHeaderProps()} />
134
+ {headers.map((header, i) => (
135
+ <TableHeader
136
+ key={i}
137
+ {...getHeaderProps({
138
+ header,
139
+ })}>
140
+ {header.header}
141
+ </TableHeader>
142
+ ))}
143
+ </TableRow>
144
+ </TableHead>
145
+ <TableBody>
146
+ {rows.map((row, i) => (
147
+ <React.Fragment key={row.id}>
148
+ <TableExpandRow {...getRowProps({ row })}>
149
+ {row.cells.map((cell) => (
150
+ <TableCell key={cell.id}>{cell.value}</TableCell>
151
+ ))}
152
+ </TableExpandRow>
153
+ {row.isExpanded ? (
154
+ <TableExpandedRow
155
+ className={styles.expandedRow}
156
+ colSpan={headers.length + 1}
157
+ {...getExpandedRowProps({ row })}>
158
+ <div className={styles.container} key={i}>
159
+ {tableRows?.[i]?.observations && tableRows[i].observations.length > 0 ? (
160
+ <div className={styles.copy}>
161
+ {tableRows[i].observations.map((obs, obsIndex) => (
162
+ <div key={obsIndex} className={styles.observationRow}>
163
+ <div className={styles.observationLabel}>{obs.conceptName}:</div>
164
+ <div className={styles.observationValue}>{obs.value}</div>
165
+ </div>
166
+ ))}
167
+ <span className={styles.metadata}>
168
+ {formatTime(new Date(tableRows?.[i]?.encounterDate))} &middot;{' '}
169
+ {tableRows?.[i]?.encounterProvider}
170
+ {tableRows?.[i]?.encounterProviderRole
171
+ ? `, ${tableRows?.[i]?.encounterProviderRole}`
172
+ : ''}
173
+ </span>
174
+ </div>
175
+ ) : (
176
+ <span className={styles.copy}>
177
+ {t('noTransferDataToDisplay', 'No transfer data to display')}
178
+ </span>
179
+ )}
180
+ </div>
181
+ </TableExpandedRow>
182
+ ) : (
183
+ <TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
184
+ )}
185
+ </React.Fragment>
186
+ ))}
187
+ </TableBody>
188
+ </Table>
189
+ </TableContainer>
190
+ )}
191
+ </DataTable>
192
+ <PatientChartPagination
193
+ pageNumber={currentPage}
194
+ totalItems={notes.length}
195
+ currentItems={paginatedNotes.length}
196
+ pageSize={pageSize}
197
+ onPageNumberChange={({ page }) => goTo(page)}
198
+ dashboardLinkUrl={pageUrl}
199
+ dashboardLinkLabel={urlLabel}
200
+ />
201
+ </div>
202
+ );
203
+ };
204
+
205
+ export default TransferNotesTable;
@@ -0,0 +1,93 @@
1
+ import { openmrsFetch, restBaseUrl, useConfig, type Encounter, type Obs } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { useMemo } from 'react';
4
+ import type { ClinicalWorkflowConfig } from '../config-schema';
5
+
6
+ export interface TransferObservation {
7
+ conceptUuid: string;
8
+ conceptName: string;
9
+ value: string;
10
+ obsDatetime: string;
11
+ }
12
+
13
+ export interface TransferNote {
14
+ id: string;
15
+ encounterUuid: string;
16
+ encounterDate: string;
17
+ transferDate: string;
18
+ fromLocation: string;
19
+ encounterProvider: string;
20
+ encounterProviderRole: string;
21
+ observations: TransferObservation[];
22
+ }
23
+
24
+ export function useTransferNotes(patientUuid: string, visitUuid: string | null) {
25
+ const config = useConfig<ClinicalWorkflowConfig>();
26
+ const { transferEncounterTypeUuid } = config;
27
+ const customRepresentation =
28
+ 'custom:(uuid,display,encounterDatetime,patient,obs:(uuid,concept:(uuid,display),value,obsDatetime),' +
29
+ 'encounterProviders:(uuid,display,' +
30
+ 'encounterRole:(uuid,display),' +
31
+ 'provider:(uuid,person:(uuid,display))),' +
32
+ 'location:(uuid,display),visit:(uuid))';
33
+
34
+ // Only fetch transfer encounters for the current visit
35
+ const encountersApiUrl = visitUuid
36
+ ? `${restBaseUrl}/encounter?patient=${patientUuid}&visit=${visitUuid}&encounterType=${transferEncounterTypeUuid}&v=${customRepresentation}`
37
+ : null;
38
+
39
+ const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Encounter[] } }, Error>(
40
+ encountersApiUrl,
41
+ openmrsFetch,
42
+ );
43
+
44
+ const mapTransferNoteProperties = (encounter: Encounter, index: number): TransferNote => {
45
+ const obs = encounter.obs || [];
46
+
47
+ // Dynamically map all observations from the transfer form
48
+ const observations: TransferObservation[] = obs
49
+ .filter((o) => o.concept && o.value) // Only include observations with concept and value
50
+ .map((observation) => ({
51
+ conceptUuid: observation.concept?.uuid || '',
52
+ conceptName: observation.concept?.display || 'Unknown',
53
+ value: getObsValue(observation),
54
+ obsDatetime: observation.obsDatetime || encounter.encounterDatetime,
55
+ }))
56
+ .sort((a, b) => a.conceptName.localeCompare(b.conceptName)); // Sort by concept name
57
+
58
+ return {
59
+ id: `${index}`,
60
+ encounterUuid: encounter.uuid,
61
+ encounterDate: encounter.encounterDatetime,
62
+ transferDate: encounter.encounterDatetime,
63
+ fromLocation: encounter.location?.display || 'Not specified',
64
+ encounterProvider: encounter.encounterProviders?.[0]?.provider?.person?.display || 'Unknown',
65
+ encounterProviderRole: encounter.encounterProviders?.[0]?.encounterRole?.display || '',
66
+ observations,
67
+ };
68
+ };
69
+
70
+ const formattedTransferNotes = data?.data?.results
71
+ ?.map(mapTransferNoteProperties)
72
+ ?.sort((noteA, noteB) => new Date(noteB.encounterDate).getTime() - new Date(noteA.encounterDate).getTime());
73
+
74
+ return {
75
+ transferNotes: data ? formattedTransferNotes : null,
76
+ error,
77
+ isLoading,
78
+ isValidating,
79
+ mutateTransferNotes: mutate,
80
+ };
81
+ }
82
+
83
+ function getObsValue(observation: Obs): string {
84
+ if (!observation) {
85
+ return '';
86
+ }
87
+
88
+ if (typeof observation.value === 'object' && observation.value !== null) {
89
+ return (observation.value as any).display || String(observation.value);
90
+ }
91
+
92
+ return String(observation.value || '');
93
+ }
package/src/routes.json CHANGED
@@ -71,6 +71,20 @@
71
71
  },
72
72
  "online": true,
73
73
  "offline": true
74
+ },
75
+ {
76
+ "name": "queue-table-transfer-column",
77
+ "component": "queueTableTransferColumn",
78
+ "slot": "queue-table-transfer-status-slot"
79
+ },
80
+ {
81
+ "name": "transfer-notes-overview-widget",
82
+ "component": "transferNotesOverview",
83
+ "slot": "patient-chart-summary-dashboard-slot",
84
+ "meta": {
85
+ "fullWidth": false
86
+ },
87
+ "order": 6
74
88
  }
75
89
  ],
76
90
  "workspaces": [
@@ -101,6 +115,11 @@
101
115
  "component": "@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace",
102
116
  "window": "clinical-workflow-window"
103
117
  },
118
+ {
119
+ "name": "patient-transfer-form-entry-workspace",
120
+ "component": "@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace",
121
+ "window": "patient-transfer"
122
+ },
104
123
  {
105
124
  "name": "visit-notes-form-shadow-workspace",
106
125
  "component": "visitNotesFormWorkspace",
@@ -119,6 +138,14 @@
119
138
  "group": "patient-chart",
120
139
  "icon": "visitNoteActionButton",
121
140
  "order": 3
141
+ },
142
+ {
143
+ "name": "patient-transfer",
144
+ "group": "patient-chart",
145
+ "icon": "patientTransferActionButton",
146
+ "order": 4,
147
+ "width": "wider",
148
+ "canMaximize": true
122
149
  }
123
150
  ],
124
151
  "workspaceGroups2": [
@@ -127,5 +154,15 @@
127
154
  "overlay": true,
128
155
  "persistence": "closable"
129
156
  }
157
+ ],
158
+ "modals": [
159
+ {
160
+ "name": "confirm-transfer-dialog",
161
+ "component": "confirmTransferDialog"
162
+ },
163
+ {
164
+ "name": "patient-transfer-details-modal",
165
+ "component": "patientTransferDetailsModal"
166
+ }
130
167
  ]
131
168
  }
@@ -0,0 +1,15 @@
1
+ export interface QueueEntry {
2
+ uuid: string;
3
+ patient: {
4
+ uuid: string;
5
+ display?: string;
6
+ };
7
+ visit: {
8
+ uuid: string;
9
+ display?: string;
10
+ };
11
+ status?: {
12
+ uuid: string;
13
+ display?: string;
14
+ };
15
+ }
@@ -73,6 +73,7 @@
73
73
  "free": "Free",
74
74
  "freeDetails": "Free Details",
75
75
  "fromDateToDate": "{{fromDate}} - {{toDate}}",
76
+ "fromLocation": "Transfer From",
76
77
  "gender": "Gender",
77
78
  "id": "ID",
78
79
  "identifier": "Identifier",
@@ -92,6 +93,7 @@
92
93
  "medicoLegalCases": "Medico Legal Cases",
93
94
  "middleName": "Middle Name",
94
95
  "months": "Months",
96
+ "moveToQueueQuestion": "Do you want to move the patient to a queue now?",
95
97
  "name": "Name",
96
98
  "nextPage": "Next page",
97
99
  "noDiagnosesFound": "No diagnoses found",
@@ -117,6 +119,7 @@
117
119
  "patientRegistrationError": "Error registering patient",
118
120
  "patientRegistrationErrorSubtitle": "Please try again.",
119
121
  "patientRegistrationSuccess": "Patient registered successfully",
122
+ "patientTransferInformation": "Patient Transfer Information",
120
123
  "paymentMethods": "Payment Methods",
121
124
  "presumed": "Presumed",
122
125
  "previousPage": "Previous page",
@@ -150,6 +153,10 @@
150
153
  "timeCompleted": "Time completed",
151
154
  "timeline": "Timeline",
152
155
  "timeStarted": "Time started",
156
+ "transferDate": "Transfer Date",
157
+ "transferFormSaved": "Patient transfer form saved",
158
+ "transferNote": "Transfer Note",
159
+ "transferPatient": "Transfer Patient",
153
160
  "triageDashboard": "Triage Dashboard",
154
161
  "triageForm": "Triage form",
155
162
  "updateVisitWithBillingInfo": "Update Visit With Billing Information",
@@ -164,5 +171,6 @@
164
171
  "Visits": "Visits",
165
172
  "visitType": "Visit Type",
166
173
  "years": "Years",
174
+ "yesMoveToQueue": "Yes, move to queue",
167
175
  "zone": "Zone"
168
176
  }
@@ -73,6 +73,7 @@
73
73
  "free": "Free",
74
74
  "freeDetails": "Free Details",
75
75
  "fromDateToDate": "{{fromDate}} - {{toDate}}",
76
+ "fromLocation": "Transfer From",
76
77
  "gender": "Gender",
77
78
  "id": "ID",
78
79
  "identifier": "Identifier",
@@ -92,6 +93,7 @@
92
93
  "medicoLegalCases": "Medico Legal Cases",
93
94
  "middleName": "Middle Name",
94
95
  "months": "Months",
96
+ "moveToQueueQuestion": "Do you want to move the patient to a queue now?",
95
97
  "name": "Name",
96
98
  "nextPage": "Next page",
97
99
  "noDiagnosesFound": "No diagnoses found",
@@ -117,6 +119,7 @@
117
119
  "patientRegistrationError": "Error registering patient",
118
120
  "patientRegistrationErrorSubtitle": "Please try again.",
119
121
  "patientRegistrationSuccess": "Patient registered successfully",
122
+ "patientTransferInformation": "Patient Transfer Information",
120
123
  "paymentMethods": "Payment Methods",
121
124
  "presumed": "Presumed",
122
125
  "previousPage": "Previous page",
@@ -150,6 +153,10 @@
150
153
  "timeCompleted": "Time completed",
151
154
  "timeline": "Timeline",
152
155
  "timeStarted": "Time started",
156
+ "transferDate": "Transfer Date",
157
+ "transferFormSaved": "Patient transfer form saved",
158
+ "transferNote": "Transfer Note",
159
+ "transferPatient": "Transfer Patient",
153
160
  "triageDashboard": "Triage Dashboard",
154
161
  "triageForm": "Triage form",
155
162
  "updateVisitWithBillingInfo": "Update Visit With Billing Information",
@@ -164,5 +171,6 @@
164
171
  "Visits": "Visits",
165
172
  "visitType": "Visit Type",
166
173
  "years": "Years",
174
+ "yesMoveToQueue": "Yes, move to queue",
167
175
  "zone": "Zone"
168
176
  }