@kenyaemr/esm-morgue-app 5.4.2-pre.2333 → 5.4.2-pre.2347
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 +20 -25
- package/dist/1.js +2 -0
- package/dist/1.js.map +1 -0
- package/dist/197.js +2 -1
- package/dist/197.js.map +1 -0
- package/dist/221.js +1 -1
- package/dist/221.js.map +1 -1
- package/dist/293.js +1 -1
- package/dist/294.js +1 -1
- package/dist/300.js +1 -1
- package/dist/340.js +2 -0
- package/dist/340.js.map +1 -0
- package/dist/351.js +1 -0
- package/dist/351.js.map +1 -0
- package/dist/404.js +1 -0
- package/dist/404.js.map +1 -0
- package/dist/441.js +1 -0
- package/dist/441.js.map +1 -0
- package/dist/578.js +1 -0
- package/dist/608.js +1 -0
- package/dist/608.js.map +1 -0
- package/dist/611.js +2 -0
- package/dist/611.js.map +1 -0
- package/dist/632.js +1 -1
- package/dist/632.js.map +1 -1
- package/dist/653.js +1 -1
- package/dist/653.js.map +1 -1
- package/dist/758.js +1 -0
- package/dist/758.js.map +1 -0
- package/dist/805.js +1 -1
- package/dist/805.js.map +1 -1
- package/dist/814.js +2 -0
- package/dist/814.js.LICENSE.txt +5 -0
- package/dist/814.js.map +1 -0
- package/dist/824.js +1 -1
- package/dist/824.js.map +1 -1
- package/dist/845.js +1 -0
- package/dist/845.js.map +1 -0
- package/dist/888.js +1 -0
- package/dist/888.js.map +1 -0
- package/dist/918.js +1 -1
- package/dist/918.js.map +1 -1
- package/dist/990.js +1 -0
- package/dist/990.js.map +1 -0
- package/dist/kenyaemr-esm-morgue-app.js +1 -1
- package/dist/kenyaemr-esm-morgue-app.js.buildmanifest.json +250 -203
- package/dist/kenyaemr-esm-morgue-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +0 -6
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/bed/bed.component.tsx +63 -134
- package/src/bed/components/deceased-patient-card-header.component.tsx +73 -0
- package/src/bed/components/deceased-patient-info.component.tsx +47 -0
- package/src/bed/components/deceased-patient-status-footer.component.tsx +43 -0
- package/src/bed-layout/admitted/admitted-bed-layout.component.tsx +175 -96
- package/src/bed-layout/awaiting/awaiting-bed-layout.component.tsx +103 -36
- package/src/bed-layout/bed-layout.scss +4 -0
- package/src/bed-layout/discharged/discharged-bed-layout.component.tsx +131 -73
- package/src/bed-linelist-view/admitted/admitted-bed-linelist-view.component.tsx +182 -134
- package/src/bed-linelist-view/awaiting/awaiting-bed-linelist-view.component.tsx +115 -71
- package/src/bed-linelist-view/discharged/discharged-bed-line-view.component.tsx +181 -109
- package/src/config-schema.ts +140 -4
- package/src/context/deceased-person-context.tsx +33 -0
- package/src/extension/actionButton.component.tsx +1 -1
- package/src/forms/admit-deceased-person-workspace/admit-deceased-person.resource.ts +84 -166
- package/src/forms/admit-deceased-person-workspace/admit-deceased-person.scss +14 -0
- package/src/forms/admit-deceased-person-workspace/admit-deceased-person.workspace.tsx +504 -334
- package/src/forms/discharge-deceased-person-workspace/discharge-body.resource.ts +0 -1
- package/src/forms/discharge-deceased-person-workspace/discharge-body.scss +15 -0
- package/src/forms/discharge-deceased-person-workspace/discharge-body.workspace.tsx +303 -244
- package/src/helpers/expression-helper.ts +122 -0
- package/src/home/home.component.tsx +23 -4
- package/src/index.ts +0 -2
- package/src/metrics/metrics-card.component.tsx +2 -2
- package/src/routes.json +0 -6
- package/src/schemas/index.ts +243 -51
- package/src/summary/summary.component.tsx +16 -9
- package/src/switcher/content-switcher.component.tsx +61 -35
- package/src/switcher/content-switcher.scss +13 -0
- package/src/types/index.ts +43 -1
- package/translations/am.json +16 -6
- package/translations/en.json +16 -6
- package/translations/sw.json +16 -6
- package/dist/373.js +0 -2
- package/dist/373.js.map +0 -1
- package/dist/398.js +0 -1
- package/dist/398.js.map +0 -1
- package/dist/410.js +0 -1
- package/dist/410.js.map +0 -1
- package/dist/429.js +0 -2
- package/dist/429.js.map +0 -1
- package/dist/467.js +0 -1
- package/dist/467.js.map +0 -1
- package/dist/579.js +0 -2
- package/dist/579.js.map +0 -1
- package/dist/619.js +0 -1
- package/dist/619.js.map +0 -1
- package/dist/633.js +0 -1
- package/dist/633.js.map +0 -1
- package/dist/712.js +0 -1
- package/dist/712.js.map +0 -1
- package/dist/713.js +0 -1
- package/dist/713.js.map +0 -1
- package/dist/723.js +0 -1
- package/dist/723.js.map +0 -1
- package/dist/989.js +0 -2
- package/dist/989.js.map +0 -1
- package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.resource.ts +0 -18
- package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.scss +0 -84
- package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.workspace.tsx +0 -505
- /package/dist/{373.js.LICENSE.txt → 1.js.LICENSE.txt} +0 -0
- /package/dist/{989.js.LICENSE.txt → 197.js.LICENSE.txt} +0 -0
- /package/dist/{579.js.LICENSE.txt → 340.js.LICENSE.txt} +0 -0
- /package/dist/{429.js.LICENSE.txt → 611.js.LICENSE.txt} +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// AwaitingBedLineListView.tsx
|
|
2
1
|
import React, { useState, useMemo } from 'react';
|
|
3
2
|
import { useTranslation } from 'react-i18next';
|
|
4
3
|
import {
|
|
@@ -16,12 +15,14 @@ import {
|
|
|
16
15
|
OverflowMenu,
|
|
17
16
|
OverflowMenuItem,
|
|
18
17
|
DataTableSkeleton,
|
|
18
|
+
Search,
|
|
19
19
|
} from '@carbon/react';
|
|
20
20
|
import styles from '../bed-linelist-view.scss';
|
|
21
21
|
import { formatDateTime } from '../../utils/utils';
|
|
22
22
|
import { type MortuaryLocationResponse, type MortuaryPatient } from '../../types';
|
|
23
|
-
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
23
|
+
import { launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
|
|
24
24
|
import { useAwaitingPatients } from '../../home/home.resource';
|
|
25
|
+
import EmptyMorgueAdmission from '../../empty-state/empty-morgue-admission.component';
|
|
25
26
|
|
|
26
27
|
interface AwaitingBedLineListViewProps {
|
|
27
28
|
awaitingQueueDeceasedPatients: Array<MortuaryPatient>;
|
|
@@ -43,9 +44,12 @@ const AwaitingBedLineListView: React.FC<AwaitingBedLineListViewProps> = ({
|
|
|
43
44
|
mutated,
|
|
44
45
|
}) => {
|
|
45
46
|
const { t } = useTranslation();
|
|
47
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
48
|
+
const controlSize = isTablet ? 'md' : 'sm';
|
|
46
49
|
|
|
47
50
|
const [currentPage, setCurrentPage] = useState(1);
|
|
48
51
|
const [currPageSize, setCurrPageSize] = useState(initialPageSize);
|
|
52
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
49
53
|
|
|
50
54
|
const trulyAwaitingPatients = useAwaitingPatients(awaitingQueueDeceasedPatients);
|
|
51
55
|
|
|
@@ -82,31 +86,51 @@ const AwaitingBedLineListView: React.FC<AwaitingBedLineListViewProps> = ({
|
|
|
82
86
|
const age = mortuaryPatient?.person?.person?.age || '-';
|
|
83
87
|
const dateOfDeath = mortuaryPatient?.person?.person?.deathDate;
|
|
84
88
|
const daysInQueue = calculateDaysInQueue(dateOfDeath);
|
|
89
|
+
const idNumber =
|
|
90
|
+
mortuaryPatient?.person?.identifiers
|
|
91
|
+
?.find((id) => id.display?.includes('OpenMRS ID'))
|
|
92
|
+
?.display?.split('=')?.[1]
|
|
93
|
+
?.trim() || '-';
|
|
85
94
|
|
|
86
95
|
return {
|
|
87
96
|
id: patientUuid,
|
|
88
97
|
admissionDate: formatDateTime(dateOfDeath),
|
|
89
|
-
idNumber
|
|
90
|
-
mortuaryPatient?.person?.identifiers
|
|
91
|
-
?.find((id) => id.display?.includes('OpenMRS ID'))
|
|
92
|
-
?.display?.split('=')?.[1]
|
|
93
|
-
?.trim() || '-',
|
|
98
|
+
idNumber,
|
|
94
99
|
name: patientName,
|
|
95
100
|
gender: gender,
|
|
96
101
|
age: age.toString(),
|
|
97
102
|
bedNumber: '-',
|
|
98
103
|
daysAdmitted: daysInQueue.toString(),
|
|
99
104
|
action: patientUuid,
|
|
105
|
+
searchableText: `${patientName} ${idNumber} ${gender}`.toLowerCase(),
|
|
100
106
|
};
|
|
101
107
|
});
|
|
102
108
|
|
|
103
109
|
return rows;
|
|
104
110
|
}, [trulyAwaitingPatients]);
|
|
105
111
|
|
|
106
|
-
const
|
|
112
|
+
const filteredRows = useMemo(() => {
|
|
113
|
+
if (!searchTerm.trim()) {
|
|
114
|
+
return allRows;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const searchLower = searchTerm.toLowerCase().trim();
|
|
118
|
+
return allRows.filter(
|
|
119
|
+
(row) =>
|
|
120
|
+
row.searchableText.includes(searchLower) ||
|
|
121
|
+
row.name.toLowerCase().includes(searchLower) ||
|
|
122
|
+
row.idNumber.toLowerCase().includes(searchLower) ||
|
|
123
|
+
row.gender.toLowerCase().includes(searchLower),
|
|
124
|
+
);
|
|
125
|
+
}, [allRows, searchTerm]);
|
|
126
|
+
|
|
127
|
+
const hasSearchTerm = searchTerm.trim().length > 0;
|
|
128
|
+
const hasNoSearchResults = hasSearchTerm && filteredRows.length === 0;
|
|
129
|
+
|
|
130
|
+
const totalCount = filteredRows.length;
|
|
107
131
|
const startIndex = (currentPage - 1) * currPageSize;
|
|
108
132
|
const endIndex = startIndex + currPageSize;
|
|
109
|
-
const paginatedRows = paginated ?
|
|
133
|
+
const paginatedRows = paginated ? filteredRows.slice(startIndex, endIndex) : filteredRows;
|
|
110
134
|
|
|
111
135
|
const handleAdmit = (patientData: MortuaryPatient) => {
|
|
112
136
|
launchWorkspace('admit-deceased-person-form', {
|
|
@@ -117,7 +141,7 @@ const AwaitingBedLineListView: React.FC<AwaitingBedLineListViewProps> = ({
|
|
|
117
141
|
});
|
|
118
142
|
};
|
|
119
143
|
|
|
120
|
-
const handleCancel = (
|
|
144
|
+
const handleCancel = () => {
|
|
121
145
|
// TODO: Implement cancel functionality
|
|
122
146
|
};
|
|
123
147
|
|
|
@@ -135,6 +159,11 @@ const AwaitingBedLineListView: React.FC<AwaitingBedLineListViewProps> = ({
|
|
|
135
159
|
}
|
|
136
160
|
};
|
|
137
161
|
|
|
162
|
+
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
163
|
+
setSearchTerm(event.target.value);
|
|
164
|
+
setCurrentPage(1);
|
|
165
|
+
};
|
|
166
|
+
|
|
138
167
|
if (isLoading) {
|
|
139
168
|
return (
|
|
140
169
|
<div className={styles.loadingContainer}>
|
|
@@ -153,69 +182,84 @@ const AwaitingBedLineListView: React.FC<AwaitingBedLineListViewProps> = ({
|
|
|
153
182
|
|
|
154
183
|
return (
|
|
155
184
|
<div className={styles.bedLayoutWrapper}>
|
|
156
|
-
<
|
|
157
|
-
{(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{
|
|
183
|
-
|
|
184
|
-
<OverflowMenu flipped>
|
|
185
|
-
<OverflowMenuItem
|
|
186
|
-
onClick={() => handleAdmit(patientData)}
|
|
187
|
-
itemText={t('admit', 'Admit')}
|
|
188
|
-
disabled={!patientData}
|
|
189
|
-
/>
|
|
190
|
-
<OverflowMenuItem
|
|
191
|
-
onClick={() => handleCancel(row.id, patientName)}
|
|
192
|
-
itemText={t('cancel', 'Cancel')}
|
|
193
|
-
/>
|
|
194
|
-
</OverflowMenu>
|
|
195
|
-
</div>
|
|
196
|
-
) : (
|
|
197
|
-
cell.value
|
|
198
|
-
)}
|
|
199
|
-
</TableCell>
|
|
185
|
+
<Search
|
|
186
|
+
labelText={t('noSearchDeceasedPatients', 'Search deceased patients')}
|
|
187
|
+
placeholder={t('searchPatientsPlaceholder', 'Search by name, ID number, or gender...')}
|
|
188
|
+
value={searchTerm}
|
|
189
|
+
onChange={handleSearchChange}
|
|
190
|
+
size={controlSize}
|
|
191
|
+
/>
|
|
192
|
+
{hasNoSearchResults ? (
|
|
193
|
+
<EmptyMorgueAdmission
|
|
194
|
+
title={t('noSearchResults', 'We couldn’t find anything')}
|
|
195
|
+
subTitle={t('tryAgain', 'Try adjusting your search {{searchTerm}} and try again', { searchTerm })}
|
|
196
|
+
/>
|
|
197
|
+
) : (
|
|
198
|
+
<>
|
|
199
|
+
<DataTable rows={paginatedRows} headers={headers} isSortable useZebraStyles>
|
|
200
|
+
{({ rows, headers, getHeaderProps, getRowProps, getTableProps, getCellProps }) => (
|
|
201
|
+
<TableContainer>
|
|
202
|
+
<Table {...getTableProps()} aria-label="deceased patients table">
|
|
203
|
+
<TableHead>
|
|
204
|
+
<TableRow>
|
|
205
|
+
{headers.map((header) => (
|
|
206
|
+
<TableHeader
|
|
207
|
+
key={header.key}
|
|
208
|
+
{...getHeaderProps({
|
|
209
|
+
header,
|
|
210
|
+
})}>
|
|
211
|
+
{header.header}
|
|
212
|
+
</TableHeader>
|
|
200
213
|
))}
|
|
201
214
|
</TableRow>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
</TableHead>
|
|
216
|
+
<TableBody>
|
|
217
|
+
{rows.map((row) => {
|
|
218
|
+
const patientData = trulyAwaitingPatients.find(
|
|
219
|
+
(patient) => patient?.person?.person?.uuid === row.id,
|
|
220
|
+
);
|
|
221
|
+
const patientName = patientData?.person?.person?.display || '';
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<TableRow key={row.id} {...getRowProps({ row })}>
|
|
225
|
+
{row.cells.map((cell) => (
|
|
226
|
+
<TableCell key={cell.id} {...getCellProps({ cell })}>
|
|
227
|
+
{cell.info.header === 'action' ? (
|
|
228
|
+
<div className={styles.actionButtons}>
|
|
229
|
+
<OverflowMenu flipped>
|
|
230
|
+
<OverflowMenuItem
|
|
231
|
+
onClick={() => handleAdmit(patientData)}
|
|
232
|
+
itemText={t('admit', 'Admit')}
|
|
233
|
+
disabled={!patientData}
|
|
234
|
+
/>
|
|
235
|
+
<OverflowMenuItem onClick={() => handleCancel()} itemText={t('cancel', 'Cancel')} />
|
|
236
|
+
</OverflowMenu>
|
|
237
|
+
</div>
|
|
238
|
+
) : (
|
|
239
|
+
cell.value
|
|
240
|
+
)}
|
|
241
|
+
</TableCell>
|
|
242
|
+
))}
|
|
243
|
+
</TableRow>
|
|
244
|
+
);
|
|
245
|
+
})}
|
|
246
|
+
</TableBody>
|
|
247
|
+
</Table>
|
|
248
|
+
</TableContainer>
|
|
249
|
+
)}
|
|
250
|
+
</DataTable>
|
|
251
|
+
|
|
252
|
+
{paginated && !isLoading && totalCount > 0 && (
|
|
253
|
+
<Pagination
|
|
254
|
+
page={currentPage}
|
|
255
|
+
pageSize={currPageSize}
|
|
256
|
+
pageSizes={pageSizes}
|
|
257
|
+
totalItems={totalCount}
|
|
258
|
+
size={'sm'}
|
|
259
|
+
onChange={handlePaginationChange}
|
|
260
|
+
/>
|
|
261
|
+
)}
|
|
262
|
+
</>
|
|
219
263
|
)}
|
|
220
264
|
</div>
|
|
221
265
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useMemo } from 'react';
|
|
1
|
+
import React, { useState, useMemo, useCallback } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import {
|
|
4
4
|
DataTable,
|
|
@@ -15,14 +15,16 @@ import {
|
|
|
15
15
|
OverflowMenu,
|
|
16
16
|
OverflowMenuItem,
|
|
17
17
|
DataTableSkeleton,
|
|
18
|
+
Search,
|
|
18
19
|
} from '@carbon/react';
|
|
19
|
-
import { ExtensionSlot, PrinterIcon, showModal, useConfig } from '@openmrs/esm-framework';
|
|
20
|
+
import { ExtensionSlot, PrinterIcon, showModal, useConfig, useLayoutType } from '@openmrs/esm-framework';
|
|
20
21
|
import styles from '../bed-linelist-view.scss';
|
|
21
22
|
import { formatDateTime } from '../../utils/utils';
|
|
22
23
|
import { type Patient, type MortuaryLocationResponse } from '../../types';
|
|
23
24
|
import { ConfigObject } from '../../config-schema';
|
|
24
25
|
import usePatients, { useMortuaryDischargeEncounter } from '../../bed-layout/discharged/discharged-bed-layout.resource';
|
|
25
26
|
import { EmptyState } from '@openmrs/esm-patient-common-lib';
|
|
27
|
+
import EmptyMorgueAdmission from '../../empty-state/empty-morgue-admission.component';
|
|
26
28
|
import { Printer } from '@carbon/react/icons';
|
|
27
29
|
|
|
28
30
|
interface DischargedBedLineListViewProps {
|
|
@@ -46,9 +48,12 @@ const DischargedBedLineListView: React.FC<DischargedBedLineListViewProps> = ({
|
|
|
46
48
|
}) => {
|
|
47
49
|
const { t } = useTranslation();
|
|
48
50
|
const { morgueDischargeEncounterTypeUuid } = useConfig<ConfigObject>();
|
|
51
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
52
|
+
const controlSize = isTablet ? 'md' : 'sm';
|
|
49
53
|
|
|
50
54
|
const [currentPage, setCurrentPage] = useState(1);
|
|
51
55
|
const [currPageSize, setCurrPageSize] = useState(initialPageSize);
|
|
56
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
52
57
|
|
|
53
58
|
const {
|
|
54
59
|
dischargedPatientUuids,
|
|
@@ -75,7 +80,7 @@ const DischargedBedLineListView: React.FC<DischargedBedLineListViewProps> = ({
|
|
|
75
80
|
{ key: 'action', header: t('action', 'Action') },
|
|
76
81
|
];
|
|
77
82
|
|
|
78
|
-
const calculateDaysSinceDeath = (dateOfDeath: string): number => {
|
|
83
|
+
const calculateDaysSinceDeath = useCallback((dateOfDeath: string): number => {
|
|
79
84
|
if (!dateOfDeath) {
|
|
80
85
|
return 0;
|
|
81
86
|
}
|
|
@@ -83,29 +88,34 @@ const DischargedBedLineListView: React.FC<DischargedBedLineListViewProps> = ({
|
|
|
83
88
|
const currentDate = new Date();
|
|
84
89
|
const timeDiff = currentDate.getTime() - deathDate.getTime();
|
|
85
90
|
return Math.floor(timeDiff / (1000 * 3600 * 24));
|
|
86
|
-
};
|
|
91
|
+
}, []);
|
|
87
92
|
|
|
88
|
-
const getEncounterDateForPatient = (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const patientEncounter = encounters.find((encounter) => encounter.patient?.uuid === patientUuid);
|
|
93
|
+
const getEncounterDateForPatient = useCallback(
|
|
94
|
+
(patientUuid: string): string | null => {
|
|
95
|
+
if (!encounters || encounters.length === 0) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
const patientEncounter = encounters.find((encounter) => encounter.patient?.uuid === patientUuid);
|
|
100
|
+
return patientEncounter?.encounterDateTime || null;
|
|
101
|
+
},
|
|
102
|
+
[encounters],
|
|
103
|
+
);
|
|
97
104
|
|
|
98
|
-
const handlePrintGatePass = (
|
|
99
|
-
|
|
100
|
-
onPrintGatePass
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
const handlePrintGatePass = useCallback(
|
|
106
|
+
(patient: Patient, encounterDate?: string) => {
|
|
107
|
+
if (onPrintGatePass) {
|
|
108
|
+
onPrintGatePass(patient, encounterDate);
|
|
109
|
+
} else {
|
|
110
|
+
const dispose = showModal('print-confirmation-modal', {
|
|
111
|
+
onClose: () => dispose(),
|
|
112
|
+
patient: patient,
|
|
113
|
+
encounterDate: encounterDate,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
[onPrintGatePass],
|
|
118
|
+
);
|
|
109
119
|
|
|
110
120
|
const allRows = useMemo(() => {
|
|
111
121
|
if (!dischargedPatients || dischargedPatients.length === 0) {
|
|
@@ -121,16 +131,17 @@ const DischargedBedLineListView: React.FC<DischargedBedLineListViewProps> = ({
|
|
|
121
131
|
const dateOfDeath = patient?.person?.deathDate;
|
|
122
132
|
const daysSinceDeath = calculateDaysSinceDeath(dateOfDeath);
|
|
123
133
|
const encounterDate = getEncounterDateForPatient(patientUuid);
|
|
134
|
+
const idNumber =
|
|
135
|
+
patient?.identifiers
|
|
136
|
+
?.find((id) => id.display?.includes('OpenMRS ID'))
|
|
137
|
+
?.display?.split('=')?.[1]
|
|
138
|
+
?.trim() || '-';
|
|
124
139
|
|
|
125
140
|
return {
|
|
126
141
|
id: patientUuid,
|
|
127
142
|
patient: patient,
|
|
128
143
|
encounterDate: encounterDate,
|
|
129
|
-
idNumber
|
|
130
|
-
patient?.identifiers
|
|
131
|
-
?.find((id) => id.display?.includes('OpenMRS ID'))
|
|
132
|
-
?.display?.split('=')?.[1]
|
|
133
|
-
?.trim() || '-',
|
|
144
|
+
idNumber,
|
|
134
145
|
name: patientName,
|
|
135
146
|
gender: gender,
|
|
136
147
|
age: age.toString(),
|
|
@@ -139,32 +150,61 @@ const DischargedBedLineListView: React.FC<DischargedBedLineListViewProps> = ({
|
|
|
139
150
|
daysSinceDeath: daysSinceDeath.toString(),
|
|
140
151
|
dischargeDate: formatDateTime(encounterDate),
|
|
141
152
|
action: patientUuid,
|
|
153
|
+
searchableText: `${patientName} ${idNumber} ${gender} ${causeOfDeath}`.toLowerCase(),
|
|
142
154
|
};
|
|
143
155
|
});
|
|
144
156
|
|
|
145
157
|
return rows;
|
|
146
|
-
}, [dischargedPatients, getEncounterDateForPatient]);
|
|
158
|
+
}, [dischargedPatients, calculateDaysSinceDeath, getEncounterDateForPatient]);
|
|
147
159
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
const filteredRows = useMemo(() => {
|
|
161
|
+
if (!searchTerm.trim()) {
|
|
162
|
+
return allRows;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const searchLower = searchTerm.toLowerCase().trim();
|
|
166
|
+
return allRows.filter(
|
|
167
|
+
(row) =>
|
|
168
|
+
row.searchableText.includes(searchLower) ||
|
|
169
|
+
row.name.toLowerCase().includes(searchLower) ||
|
|
170
|
+
row.idNumber.toLowerCase().includes(searchLower) ||
|
|
171
|
+
row.gender.toLowerCase().includes(searchLower) ||
|
|
172
|
+
row.causeOfDeath.toLowerCase().includes(searchLower),
|
|
173
|
+
);
|
|
174
|
+
}, [allRows, searchTerm]);
|
|
152
175
|
|
|
153
|
-
const goTo = (page: number) => {
|
|
176
|
+
const goTo = useCallback((page: number) => {
|
|
154
177
|
setCurrentPage(page);
|
|
155
|
-
};
|
|
178
|
+
}, []);
|
|
156
179
|
|
|
157
|
-
const handlePaginationChange = (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
180
|
+
const handlePaginationChange = useCallback(
|
|
181
|
+
({ page: newPage, pageSize }: { page: number; pageSize: number }) => {
|
|
182
|
+
if (newPage !== currentPage) {
|
|
183
|
+
goTo(newPage);
|
|
184
|
+
}
|
|
185
|
+
if (pageSize !== currPageSize) {
|
|
186
|
+
setCurrPageSize(pageSize);
|
|
187
|
+
setCurrentPage(1);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
[currentPage, currPageSize, goTo],
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
194
|
+
setSearchTerm(event.target.value);
|
|
195
|
+
setCurrentPage(1);
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
const clearSearch = useCallback(() => {
|
|
199
|
+
setSearchTerm('');
|
|
200
|
+
setCurrentPage(1);
|
|
201
|
+
}, []);
|
|
166
202
|
|
|
167
203
|
const isLoadingData = isLoading || encountersLoading || patientsLoading;
|
|
204
|
+
const hasSearchTerm = searchTerm.trim().length > 0;
|
|
205
|
+
const hasNoSearchResults = hasSearchTerm && filteredRows.length === 0;
|
|
206
|
+
const hasPatients = dischargedPatients && dischargedPatients.length > 0;
|
|
207
|
+
const totalCount = filteredRows.length;
|
|
168
208
|
|
|
169
209
|
if (isLoadingData) {
|
|
170
210
|
return (
|
|
@@ -180,88 +220,120 @@ const DischargedBedLineListView: React.FC<DischargedBedLineListViewProps> = ({
|
|
|
180
220
|
<EmptyState
|
|
181
221
|
headerTitle={t('noDischargedPatients', 'No discharged patients found')}
|
|
182
222
|
displayText={t('noDischargedPatientsDescription', 'There are currently no discharged patients to display.')}
|
|
183
|
-
/>
|
|
223
|
+
/>
|
|
184
224
|
</div>
|
|
185
225
|
);
|
|
186
226
|
}
|
|
187
227
|
|
|
188
|
-
if (!
|
|
228
|
+
if (!hasPatients) {
|
|
189
229
|
return (
|
|
190
230
|
<div className={styles.emptyState}>
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
231
|
+
<EmptyMorgueAdmission
|
|
232
|
+
title={t('noDischargedPatient', 'No deceased patients currently discharged')}
|
|
233
|
+
subTitle={t(
|
|
234
|
+
'noDischargedPatientsDescription',
|
|
235
|
+
'There are no discharged deceased patients to display at this time.',
|
|
236
|
+
)}
|
|
194
237
|
/>
|
|
195
238
|
</div>
|
|
196
239
|
);
|
|
197
240
|
}
|
|
198
241
|
|
|
242
|
+
const startIndex = (currentPage - 1) * currPageSize;
|
|
243
|
+
const endIndex = startIndex + currPageSize;
|
|
244
|
+
const paginatedRows = paginated ? filteredRows.slice(startIndex, endIndex) : filteredRows;
|
|
245
|
+
|
|
246
|
+
const NoSearchResults = () => (
|
|
247
|
+
<div className={styles.emptyState}>
|
|
248
|
+
<EmptyMorgueAdmission
|
|
249
|
+
title={t('noSearchResults', 'We couldn’t find anything')}
|
|
250
|
+
subTitle={t('tryAgain', 'Try adjusting your search {{searchTerm}} and try again', { searchTerm })}
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
|
|
199
255
|
return (
|
|
200
256
|
<div className={styles.bedLayoutWrapper}>
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
{...getHeaderProps({
|
|
211
|
-
header,
|
|
212
|
-
})}>
|
|
213
|
-
{header.header}
|
|
214
|
-
</TableHeader>
|
|
215
|
-
))}
|
|
216
|
-
</TableRow>
|
|
217
|
-
</TableHead>
|
|
218
|
-
<TableBody>
|
|
219
|
-
{rows.map((row) => {
|
|
220
|
-
const rowData = paginatedRows.find((r) => r.id === row.id);
|
|
221
|
-
const patientData = rowData?.patient;
|
|
222
|
-
const encounterDate = rowData?.encounterDate;
|
|
257
|
+
<div className={styles.searchContainer}>
|
|
258
|
+
<Search
|
|
259
|
+
labelText={t('searchPatients', 'Search Patients')}
|
|
260
|
+
placeholder={t('searchPatientsPlaceholder', 'Search by name, ID number, gender, or cause of death...')}
|
|
261
|
+
value={searchTerm}
|
|
262
|
+
onChange={handleSearchChange}
|
|
263
|
+
size={controlSize}
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
223
266
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
) : (
|
|
243
|
-
cell.value
|
|
244
|
-
)}
|
|
245
|
-
</TableCell>
|
|
267
|
+
{hasNoSearchResults ? (
|
|
268
|
+
<NoSearchResults />
|
|
269
|
+
) : (
|
|
270
|
+
<>
|
|
271
|
+
<DataTable rows={paginatedRows} headers={headers} isSortable useZebraStyles>
|
|
272
|
+
{({ rows, headers, getHeaderProps, getRowProps, getTableProps, getCellProps }) => (
|
|
273
|
+
<TableContainer>
|
|
274
|
+
<Table {...getTableProps()} aria-label="discharged patients table">
|
|
275
|
+
<TableHead>
|
|
276
|
+
<TableRow>
|
|
277
|
+
{headers.map((header) => (
|
|
278
|
+
<TableHeader
|
|
279
|
+
key={header.key}
|
|
280
|
+
{...getHeaderProps({
|
|
281
|
+
header,
|
|
282
|
+
})}>
|
|
283
|
+
{header.header}
|
|
284
|
+
</TableHeader>
|
|
246
285
|
))}
|
|
247
286
|
</TableRow>
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
</DataTable>
|
|
287
|
+
</TableHead>
|
|
288
|
+
<TableBody>
|
|
289
|
+
{rows.map((row) => {
|
|
290
|
+
const rowData = paginatedRows.find((r) => r.id === row.id);
|
|
291
|
+
const patientData = rowData?.patient;
|
|
292
|
+
const encounterDate = rowData?.encounterDate;
|
|
255
293
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
return (
|
|
295
|
+
<TableRow key={row.id} {...getRowProps({ row })}>
|
|
296
|
+
{row.cells.map((cell) => (
|
|
297
|
+
<TableCell key={cell.id} {...getCellProps({ cell })}>
|
|
298
|
+
{cell.info.header === 'action' ? (
|
|
299
|
+
<div className={styles.actionButtons}>
|
|
300
|
+
<OverflowMenu renderIcon={Printer} flipped>
|
|
301
|
+
<OverflowMenuItem
|
|
302
|
+
onClick={() => handlePrintGatePass(patientData, encounterDate)}
|
|
303
|
+
itemText={t('printGatePass', 'Gate Pass')}
|
|
304
|
+
disabled={!patientData}
|
|
305
|
+
/>
|
|
306
|
+
<ExtensionSlot
|
|
307
|
+
name="print-post-mortem-overflow-menu-item-slot"
|
|
308
|
+
state={{ patientUuid: row.id }}
|
|
309
|
+
/>
|
|
310
|
+
</OverflowMenu>
|
|
311
|
+
</div>
|
|
312
|
+
) : (
|
|
313
|
+
cell.value
|
|
314
|
+
)}
|
|
315
|
+
</TableCell>
|
|
316
|
+
))}
|
|
317
|
+
</TableRow>
|
|
318
|
+
);
|
|
319
|
+
})}
|
|
320
|
+
</TableBody>
|
|
321
|
+
</Table>
|
|
322
|
+
</TableContainer>
|
|
323
|
+
)}
|
|
324
|
+
</DataTable>
|
|
325
|
+
|
|
326
|
+
{paginated && !isLoadingData && totalCount > 0 && (
|
|
327
|
+
<Pagination
|
|
328
|
+
page={currentPage}
|
|
329
|
+
pageSize={currPageSize}
|
|
330
|
+
pageSizes={pageSizes}
|
|
331
|
+
totalItems={totalCount}
|
|
332
|
+
size={'sm'}
|
|
333
|
+
onChange={handlePaginationChange}
|
|
334
|
+
/>
|
|
335
|
+
)}
|
|
336
|
+
</>
|
|
265
337
|
)}
|
|
266
338
|
</div>
|
|
267
339
|
);
|