@kenyaemr/esm-morgue-app 5.4.2-pre.2279 → 5.4.2-pre.2288

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 (193) hide show
  1. package/.turbo/turbo-build.log +19 -19
  2. package/dist/109.js +2 -0
  3. package/dist/109.js.map +1 -0
  4. package/dist/201.js +1 -0
  5. package/dist/201.js.map +1 -0
  6. package/dist/293.js +1 -0
  7. package/dist/293.js.map +1 -0
  8. package/dist/300.js +1 -1
  9. package/dist/347.js +1 -2
  10. package/dist/347.js.map +1 -1
  11. package/dist/373.js +2 -0
  12. package/dist/373.js.map +1 -0
  13. package/dist/38.js +1 -0
  14. package/dist/38.js.map +1 -0
  15. package/dist/389.js +1 -0
  16. package/dist/389.js.map +1 -0
  17. package/dist/398.js +1 -0
  18. package/dist/398.js.map +1 -0
  19. package/dist/4.js +2 -0
  20. package/dist/4.js.map +1 -0
  21. package/dist/410.js +1 -0
  22. package/dist/410.js.map +1 -0
  23. package/dist/420.js +2 -0
  24. package/dist/420.js.map +1 -0
  25. package/dist/467.js +1 -0
  26. package/dist/467.js.map +1 -0
  27. package/dist/632.js +1 -0
  28. package/dist/632.js.map +1 -0
  29. package/dist/798.js +1 -0
  30. package/dist/798.js.map +1 -0
  31. package/dist/811.js +1 -0
  32. package/dist/811.js.map +1 -0
  33. package/dist/824.js +1 -0
  34. package/dist/824.js.map +1 -0
  35. package/dist/827.js +1 -0
  36. package/dist/827.js.map +1 -0
  37. package/dist/842.js +2 -0
  38. package/dist/842.js.LICENSE.txt +5 -0
  39. package/dist/842.js.map +1 -0
  40. package/dist/918.js +1 -1
  41. package/dist/918.js.map +1 -1
  42. package/dist/kenyaemr-esm-morgue-app.js +1 -1
  43. package/dist/kenyaemr-esm-morgue-app.js.buildmanifest.json +218 -291
  44. package/dist/kenyaemr-esm-morgue-app.js.map +1 -1
  45. package/dist/main.js +2 -1
  46. package/dist/main.js.LICENSE.txt +15 -0
  47. package/dist/main.js.map +1 -1
  48. package/dist/routes.json +1 -1
  49. package/package.json +1 -1
  50. package/src/bed/bed.component.tsx +164 -0
  51. package/src/bed/bed.scss +192 -0
  52. package/src/bed/divider/divider.component.tsx +18 -0
  53. package/src/bed/empty-bed.component.tsx +47 -0
  54. package/src/bed-layout/admitted/admitted-bed-layout.component.tsx +189 -0
  55. package/src/bed-layout/awaiting/awaiting-bed-layout.component.tsx +86 -0
  56. package/src/bed-layout/bed-layout.resource.ts +72 -0
  57. package/src/bed-layout/bed-layout.scss +55 -0
  58. package/src/bed-layout/discharged/discharged-bed-layout.component.tsx +109 -0
  59. package/src/bed-layout/discharged/discharged-bed-layout.resource.ts +99 -0
  60. package/src/bed-linelist-view/admitted/admitted-bed-linelist-view.component.tsx +420 -0
  61. package/src/bed-linelist-view/awaiting/awaiting-bed-linelist-view.component.tsx +224 -0
  62. package/src/bed-linelist-view/bed-linelist-view.scss +5 -0
  63. package/src/bed-linelist-view/discharged/discharged-bed-line-view.component.tsx +256 -0
  64. package/src/config-schema.ts +41 -9
  65. package/src/constants.ts +57 -0
  66. package/src/deceased-patient-header/deceased-patient-header.component.tsx +31 -0
  67. package/src/deceased-patient-header/deceased-patient-header.scss +50 -0
  68. package/src/{component → deceased-patient-header}/deceasedInfo/deceased-info.component.tsx +1 -1
  69. package/src/deceased-patient-header/deceasedInfo/deceased-info.resource.ts +11 -0
  70. package/src/extension/actionButton.component.tsx +5 -59
  71. package/src/extension/deceasedInfoBanner.component.tsx +5 -9
  72. package/src/{hook/useAdmitPatient.ts → forms/admit-deceased-person-workspace/admit-deceased-person.resource.ts} +177 -46
  73. package/src/forms/admit-deceased-person-workspace/admit-deceased-person.scss +143 -0
  74. package/src/forms/admit-deceased-person-workspace/admit-deceased-person.workspace.tsx +648 -0
  75. package/src/{hook/usePersonAttributes.ts → forms/discharge-deceased-person-workspace/discharge-body.resource.ts} +1 -1
  76. package/src/forms/discharge-deceased-person-workspace/discharge-body.scss +56 -0
  77. package/src/forms/discharge-deceased-person-workspace/discharge-body.workspace.tsx +362 -0
  78. package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.resource.ts +18 -0
  79. package/src/{workspaces/patientAdditionalInfoForm.scss → forms/dispose-deceased-person-workspace/dispose-deceased-person.scss} +46 -66
  80. package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.workspace.tsx +401 -0
  81. package/src/forms/form-entry-workspace/form-entry-workspace.workspace.tsx +62 -0
  82. package/src/forms/swap-compartment-workspace/swap-unit.scss +144 -0
  83. package/src/forms/swap-compartment-workspace/swap-unit.workspace.tsx +280 -0
  84. package/src/header/header.component.tsx +41 -0
  85. package/src/header/header.scss +58 -0
  86. package/src/home/home.component.tsx +87 -0
  87. package/src/home/home.resource.ts +261 -0
  88. package/src/home/home.scss +5 -0
  89. package/src/index.ts +18 -12
  90. package/src/metrics/metrics-card.component.tsx +31 -0
  91. package/src/metrics/metrics-card.scss +51 -0
  92. package/src/root.component.tsx +7 -3
  93. package/src/routes.json +25 -1
  94. package/src/schemas/index.ts +66 -0
  95. package/src/summary/summary.component.tsx +42 -0
  96. package/src/summary/summary.scss +10 -0
  97. package/src/switcher/content-switcher.component.tsx +220 -0
  98. package/src/switcher/content-switcher.scss +30 -0
  99. package/src/types/index.ts +336 -359
  100. package/src/utils/utils.ts +20 -2
  101. package/src/view-details/main/main.component.tsx +34 -0
  102. package/src/view-details/main/main.scss +45 -0
  103. package/src/view-details/panels/attachement.component.tsx +21 -0
  104. package/src/view-details/panels/autopsy.component.tsx +215 -0
  105. package/src/view-details/panels/billing-history.component.tsx +13 -0
  106. package/src/view-details/panels/observations/observation.component.tsx +57 -0
  107. package/src/view-details/panels/observations/observation.scss +24 -0
  108. package/src/view-details/panels/panels.scss +46 -0
  109. package/src/view-details/view-details.component.tsx +65 -0
  110. package/src/view-details/view-details.resource.ts +65 -0
  111. package/src/view-details/views-details.scss +82 -0
  112. package/translations/en.json +67 -25
  113. package/tsconfig.json +1 -1
  114. package/dist/113.js +0 -1
  115. package/dist/113.js.map +0 -1
  116. package/dist/160.js +0 -1
  117. package/dist/160.js.map +0 -1
  118. package/dist/299.js +0 -1
  119. package/dist/299.js.map +0 -1
  120. package/dist/433.js +0 -2
  121. package/dist/433.js.map +0 -1
  122. package/dist/441.js +0 -1
  123. package/dist/441.js.map +0 -1
  124. package/dist/496.js +0 -1
  125. package/dist/496.js.map +0 -1
  126. package/dist/511.js +0 -1
  127. package/dist/511.js.map +0 -1
  128. package/dist/603.js +0 -1
  129. package/dist/603.js.map +0 -1
  130. package/dist/610.js +0 -1
  131. package/dist/610.js.map +0 -1
  132. package/dist/612.js +0 -1
  133. package/dist/612.js.map +0 -1
  134. package/dist/656.js +0 -2
  135. package/dist/656.js.map +0 -1
  136. package/dist/752.js +0 -1
  137. package/dist/752.js.map +0 -1
  138. package/dist/754.js +0 -1
  139. package/dist/754.js.map +0 -1
  140. package/dist/781.js +0 -1
  141. package/dist/781.js.map +0 -1
  142. package/dist/801.js +0 -2
  143. package/dist/801.js.map +0 -1
  144. package/dist/817.js +0 -1
  145. package/dist/817.js.map +0 -1
  146. package/dist/877.js +0 -1
  147. package/dist/877.js.map +0 -1
  148. package/dist/924.js +0 -1
  149. package/dist/924.js.map +0 -1
  150. package/src/autosuggest/autosuggest.component.tsx +0 -162
  151. package/src/autosuggest/autosuggest.scss +0 -61
  152. package/src/autosuggest/patient-search-info.component.tsx +0 -75
  153. package/src/autosuggest/patient-search-info.scss +0 -62
  154. package/src/autosuggest/search-empty-state.component.tsx +0 -21
  155. package/src/autosuggest/search-empty-state.scss +0 -18
  156. package/src/card/avail-compartment.compartment.tsx +0 -94
  157. package/src/card/compartment-view.compartment.tsx +0 -62
  158. package/src/card/compartment.scss +0 -128
  159. package/src/card/compartmentSharing.component.tsx +0 -21
  160. package/src/card/compartmentSharing.scss +0 -24
  161. package/src/card/empty-compartment.component.tsx +0 -28
  162. package/src/card/empty-compartment.scss +0 -61
  163. package/src/component/main.component.tsx +0 -17
  164. package/src/component/next-of-kin-details/nextOfKinDetails.component.tsx +0 -50
  165. package/src/component/next-of-kin-details/nextOfKinDetails.scss +0 -37
  166. package/src/header/admitted-queue-header.component.tsx +0 -30
  167. package/src/header/admitted-queue-header.scss +0 -32
  168. package/src/header/morgue-header.component.tsx +0 -38
  169. package/src/header/morgue-header.scss +0 -95
  170. package/src/header/morgue-illustration.component.tsx +0 -13
  171. package/src/hook/useDeceasedPatients.ts +0 -12
  172. package/src/hook/useDischargedPatient.ts +0 -55
  173. package/src/hook/useMorgue.resource.ts +0 -163
  174. package/src/hook/useMortuaryAdmissionLocation.ts +0 -64
  175. package/src/tables/admitted-queue.component.tsx +0 -54
  176. package/src/tables/admitted-queue.scss +0 -62
  177. package/src/tables/discharge-queue.component.tsx +0 -87
  178. package/src/tables/generic-table.component.tsx +0 -140
  179. package/src/tables/generic-table.scss +0 -37
  180. package/src/tabs/tabs.component.tsx +0 -82
  181. package/src/tabs/tabs.scss +0 -15
  182. package/src/workspaces/admit-body.scss +0 -46
  183. package/src/workspaces/admit-body.workspace.tsx +0 -79
  184. package/src/workspaces/discharge-body.scss +0 -67
  185. package/src/workspaces/discharge-body.workspace.tsx +0 -329
  186. package/src/workspaces/patientAdditionalInfoForm.workspace.tsx +0 -562
  187. package/src/workspaces/swap-unit.scss +0 -46
  188. package/src/workspaces/swap-unit.workspace.tsx +0 -168
  189. /package/dist/{347.js.LICENSE.txt → 109.js.LICENSE.txt} +0 -0
  190. /package/dist/{656.js.LICENSE.txt → 373.js.LICENSE.txt} +0 -0
  191. /package/dist/{801.js.LICENSE.txt → 4.js.LICENSE.txt} +0 -0
  192. /package/dist/{433.js.LICENSE.txt → 420.js.LICENSE.txt} +0 -0
  193. /package/src/{component → deceased-patient-header}/deceasedInfo/deceased-info.scss +0 -0
@@ -0,0 +1,420 @@
1
+ import React, { useState, useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ DataTable,
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableContainer,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ Pagination,
13
+ OverflowMenu,
14
+ OverflowMenuItem,
15
+ Tag,
16
+ DataTableSkeleton,
17
+ } from '@carbon/react';
18
+ import { launchWorkspace, navigate, useConfig, useVisit } from '@openmrs/esm-framework';
19
+ import styles from '../bed-linelist-view.scss';
20
+ import { convertDateToDays, formatDateTime } from '../../utils/utils';
21
+ import { Patient, Person, type MortuaryLocationResponse } from '../../types';
22
+ import { ConfigObject } from '../../config-schema';
23
+ import { mutate as mutateSWR } from 'swr';
24
+ import { EmptyState } from '@openmrs/esm-patient-common-lib/src';
25
+
26
+ interface AdmittedBedLineListViewProps {
27
+ AdmittedDeceasedPatient: MortuaryLocationResponse | null;
28
+ isLoading: boolean;
29
+ paginated?: boolean;
30
+ initialPageSize?: number;
31
+ pageSizes?: number[];
32
+ onPostmortem?: (patientUuid: string) => void;
33
+ onDischarge?: (patientUuid: string) => void;
34
+ onSwapCompartment?: (patientUuid: string, bedId: string) => void;
35
+ onDispose?: (patientUuid: string) => void;
36
+ mutate?: () => void;
37
+ }
38
+
39
+ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
40
+ AdmittedDeceasedPatient,
41
+ isLoading,
42
+ paginated = true,
43
+ initialPageSize = 10,
44
+ pageSizes = [10, 20, 30, 40, 50],
45
+ onPostmortem,
46
+ onDischarge,
47
+ onSwapCompartment,
48
+ onDispose,
49
+ mutate,
50
+ }) => {
51
+ const { t } = useTranslation();
52
+ const { autopsyFormUuid } = useConfig<ConfigObject>();
53
+
54
+ const [currentPage, setCurrentPage] = useState(1);
55
+ const [currPageSize, setCurrPageSize] = useState(initialPageSize);
56
+
57
+ const DaysInMortuary = ({ patientUuid }: { patientUuid: string }) => {
58
+ const { activeVisit } = useVisit(patientUuid);
59
+ const days = convertDateToDays(activeVisit?.startDatetime);
60
+
61
+ return (
62
+ <>
63
+ {days} {days === 1 ? t('day', 'Day') : t('days', 'Days')}
64
+ </>
65
+ );
66
+ };
67
+
68
+ const AdmissionDate = ({ patientUuid }: { patientUuid: string }) => {
69
+ const { activeVisit } = useVisit(patientUuid);
70
+ return <>{activeVisit?.startDatetime ? formatDateTime(activeVisit.startDatetime) : '-'}</>;
71
+ };
72
+
73
+ const headers = [
74
+ { key: 'admissionDate', header: t('admissionDate', 'Admission Date') },
75
+ { key: 'idNumber', header: t('idNumber', 'ID Number') },
76
+ { key: 'patientName', header: t('patientName', 'Patient Name') },
77
+ { key: 'gender', header: t('gender', 'Gender') },
78
+ { key: 'age', header: t('age', 'Age') },
79
+ { key: 'bedNumber', header: t('compartmentNumber', 'Compartment number') },
80
+ { key: 'compartmentShare', header: t('compartmentShare', 'Compartment Share') },
81
+ { key: 'bedType', header: t('bedType', 'Bed Type') },
82
+ { key: 'daysAdmitted', header: t('daysInMortuary', 'Days in Mortuary') },
83
+ { key: 'status', header: t('status', 'Status') },
84
+ { key: 'action', header: t('action', 'Action') },
85
+ ];
86
+
87
+ const handlePostmortem = (patientUuid: string) => {
88
+ if (onPostmortem) {
89
+ onPostmortem(patientUuid);
90
+ } else {
91
+ launchWorkspace('mortuary-form-entry', {
92
+ formUuid: autopsyFormUuid,
93
+ workspaceTitle: t('postmortemForm', 'Postmortem form'),
94
+ patientUuid: patientUuid,
95
+ encounterUuid: '',
96
+ mutateForm: () => {
97
+ mutateSWR((key) => true, undefined, {
98
+ revalidate: true,
99
+ });
100
+ },
101
+ });
102
+ }
103
+ };
104
+
105
+ const handleDischarge = (patientUuid: string, bedId: number) => {
106
+ if (onDischarge) {
107
+ onDischarge(patientUuid);
108
+ } else {
109
+ launchWorkspace('discharge-body-form', {
110
+ workspaceTitle: t('dischargeForm', 'Discharge form'),
111
+ patientUuid: patientUuid,
112
+ bedId,
113
+ mutate,
114
+ });
115
+ }
116
+ };
117
+
118
+ const handleSwapCompartment = (patientUuid: string, bedId: number) => {
119
+ if (onSwapCompartment) {
120
+ onSwapCompartment(patientUuid, bedId.toString());
121
+ } else {
122
+ launchWorkspace('swap-unit-form', {
123
+ workspaceTitle: t('swapCompartment', 'Swap compartment'),
124
+ patientUuid: patientUuid,
125
+ bedId,
126
+ mortuaryLocation: AdmittedDeceasedPatient,
127
+ mutate,
128
+ });
129
+ }
130
+ };
131
+
132
+ const handleDispose = (patientUuid: string, bedId: number) => {
133
+ if (onDispose) {
134
+ onDispose(patientUuid);
135
+ } else {
136
+ launchWorkspace('dispose-deceased-person-form', {
137
+ workspaceTitle: t('disposeForm', 'Dispose form'),
138
+ patientUuid: patientUuid,
139
+ bedId,
140
+ mutate,
141
+ });
142
+ }
143
+ };
144
+
145
+ const calculateDaysAdmitted = (dateOfDeath: string): number => {
146
+ if (!dateOfDeath) {
147
+ return 0;
148
+ }
149
+ const deathDate = new Date(dateOfDeath);
150
+ const currentDate = new Date();
151
+ const timeDiff = currentDate.getTime() - deathDate.getTime();
152
+ return Math.floor(timeDiff / (1000 * 3600 * 24));
153
+ };
154
+
155
+ const getCompartmentShare = (patients: any[]) => {
156
+ if (!patients || patients.length === 0) {
157
+ return t('empty', 'Empty');
158
+ }
159
+ return patients.length > 1
160
+ ? t('sharedCompartment', '{{count}} sharing', { count: patients.length })
161
+ : t('singleOccupancy', 'Single');
162
+ };
163
+
164
+ const getIdNumber = (patient: Patient) => {
165
+ if (!patient?.identifiers) {
166
+ return '-';
167
+ }
168
+
169
+ const openmrsIdentifier = patient.identifiers.find(
170
+ (id) =>
171
+ (typeof id.identifierType === 'object' &&
172
+ id.identifierType &&
173
+ 'name' in id.identifierType &&
174
+ (id.identifierType as { name?: string }).name === 'OpenMRS ID') ||
175
+ (typeof id.identifierType === 'object' &&
176
+ id.identifierType &&
177
+ 'display' in id.identifierType &&
178
+ (id.identifierType as { display?: string }).display === 'OpenMRS ID') ||
179
+ id.display?.includes('OpenMRS ID'),
180
+ );
181
+ return openmrsIdentifier?.identifier || '-';
182
+ };
183
+
184
+ const getPatientName = (patient: Person) => {
185
+ if (!patient) {
186
+ return '-';
187
+ }
188
+
189
+ if (patient.display) {
190
+ return patient.display;
191
+ }
192
+
193
+ return '-';
194
+ };
195
+
196
+ const allRows = useMemo(() => {
197
+ if (isLoading) {
198
+ return [];
199
+ }
200
+
201
+ const bedLayouts = AdmittedDeceasedPatient?.bedLayouts || [];
202
+ const rows = [];
203
+
204
+ for (const bedLayout of bedLayouts) {
205
+ const patients = bedLayout.patients || [];
206
+ const bedNumber = bedLayout.bedNumber;
207
+ const bedId = bedLayout.bedId;
208
+ const bedUuid = bedLayout.bedUuid;
209
+ const bedStatus = bedLayout.status;
210
+ const bedType = bedLayout.bedType?.displayName || '-';
211
+ const compartmentShare = getCompartmentShare(patients);
212
+
213
+ if (patients.length === 0) {
214
+ rows.push({
215
+ id: bedUuid || `empty-bed-${bedId}`,
216
+ bedNumber,
217
+ compartmentShare,
218
+ bedType,
219
+ status: bedStatus,
220
+ patientName: '-',
221
+ idNumber: '-',
222
+ gender: '-',
223
+ age: '-',
224
+ dateOfDeath: '-',
225
+ causeOfDeath: '-',
226
+ daysAdmitted: '-',
227
+ isEmpty: true,
228
+ bedId,
229
+ });
230
+ } else {
231
+ for (const patient of patients) {
232
+ const patientUuid = patient.uuid;
233
+ const patientName = getPatientName(patient.person);
234
+ const gender = patient.person?.gender || '-';
235
+ const age = patient.person?.age?.toString() || '-';
236
+ const causeOfDeath = patient.person?.causeOfDeath?.display || t('unknown', 'Unknown');
237
+ const dateOfDeath = patient.person?.deathDate;
238
+ const daysAdmitted = calculateDaysAdmitted(dateOfDeath).toString();
239
+
240
+ rows.push({
241
+ id: `${bedUuid}-${patientUuid}`,
242
+ bedNumber,
243
+ compartmentShare,
244
+ bedType,
245
+ status: bedStatus,
246
+ patientName,
247
+ idNumber: getIdNumber(patient),
248
+ gender,
249
+ age,
250
+ dateOfDeath: formatDateTime(dateOfDeath),
251
+ causeOfDeath,
252
+ daysAdmitted,
253
+ isEmpty: false,
254
+ patientUuid,
255
+ bedUuid,
256
+ bedId,
257
+ });
258
+ }
259
+ }
260
+ }
261
+
262
+ return rows;
263
+ }, [getCompartmentShare, AdmittedDeceasedPatient, t, isLoading]);
264
+
265
+ if (isLoading) {
266
+ return (
267
+ <div className={styles.loadingContainer}>
268
+ <DataTableSkeleton columnCount={headers.length} rowCount={5} zebra />
269
+ </div>
270
+ );
271
+ }
272
+
273
+ if (!AdmittedDeceasedPatient) {
274
+ return (
275
+ <div className={styles.loadingContainer}>
276
+ <EmptyState
277
+ headerTitle={t('noAdmittedPatients', 'No admitted patients found')}
278
+ displayText={t('noAdmittedPatientsDescription', 'There are currently no admitted patients to display.')}
279
+ />
280
+ </div>
281
+ );
282
+ }
283
+
284
+ const totalCount = allRows.length;
285
+ const startIndex = (currentPage - 1) * currPageSize;
286
+ const endIndex = startIndex + currPageSize;
287
+ const paginatedRows = paginated ? allRows.slice(startIndex, endIndex) : allRows;
288
+
289
+ const goTo = (page: number) => setCurrentPage(page);
290
+
291
+ const handlePaginationChange = ({ page: newPage, pageSize }: { page: number; pageSize: number }) => {
292
+ if (newPage !== currentPage) {
293
+ goTo(newPage);
294
+ }
295
+ if (pageSize !== currPageSize) {
296
+ setCurrPageSize(pageSize);
297
+ setCurrentPage(1);
298
+ }
299
+ };
300
+
301
+ return (
302
+ <div className={styles.bedLayoutWrapper}>
303
+ <DataTable rows={paginatedRows} headers={headers} isSortable useZebraStyles>
304
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps, getCellProps }) => (
305
+ <TableContainer>
306
+ <Table {...getTableProps()} aria-label="mortuary beds table">
307
+ <TableHead>
308
+ <TableRow>
309
+ {headers.map((header) => (
310
+ <TableHeader key={header.key} {...getHeaderProps({ header })}>
311
+ {header.header}
312
+ </TableHeader>
313
+ ))}
314
+ </TableRow>
315
+ </TableHead>
316
+ <TableBody>
317
+ {rows.map((row) => {
318
+ const rowData = allRows.find((r) => r.id === row.id);
319
+ if (!rowData) {
320
+ return null;
321
+ }
322
+
323
+ return (
324
+ <TableRow key={row.id} {...getRowProps({ row })}>
325
+ {row.cells.map((cell) => {
326
+ const cellKey = cell.info.header as keyof typeof rowData;
327
+
328
+ if (cell.info.header === 'daysAdmitted' && !rowData.isEmpty) {
329
+ return (
330
+ <TableCell key={cell.id}>
331
+ <DaysInMortuary patientUuid={rowData.patientUuid} />
332
+ </TableCell>
333
+ );
334
+ }
335
+
336
+ if (cell.info.header === 'admissionDate' && !rowData.isEmpty) {
337
+ return (
338
+ <TableCell key={cell.id}>
339
+ <AdmissionDate patientUuid={rowData.patientUuid} />
340
+ </TableCell>
341
+ );
342
+ }
343
+
344
+ if (cell.info.header === 'status') {
345
+ return (
346
+ <TableCell key={cell.id}>
347
+ <Tag type={rowData.status === 'AVAILABLE' ? 'green' : 'red'} size="sm">
348
+ {rowData.status === 'AVAILABLE'
349
+ ? t('available', 'Available')
350
+ : t('occupied', 'Occupied')}
351
+ </Tag>
352
+ </TableCell>
353
+ );
354
+ }
355
+
356
+ if (cell.info.header === 'action') {
357
+ return (
358
+ <TableCell key={cell.id}>
359
+ {!rowData.isEmpty && (
360
+ <OverflowMenu flipped>
361
+ <OverflowMenuItem
362
+ onClick={() => {
363
+ const hasBedInfo = rowData.bedNumber && rowData.bedId;
364
+ const base = `${window.getOpenmrsSpaBase()}home/morgue/patient/${
365
+ rowData.patientUuid
366
+ }`;
367
+ const to = hasBedInfo
368
+ ? `${base}/compartment/${rowData.bedNumber}/${rowData.bedId}/mortuary-chart`
369
+ : `${base}/mortuary-chart`;
370
+ navigate({ to });
371
+ }}
372
+ itemText={t('viewDetails', 'View details')}
373
+ />
374
+ <OverflowMenuItem
375
+ onClick={() => handlePostmortem(rowData.patientUuid)}
376
+ itemText={t('postmortemForm', 'Postmortem')}
377
+ />
378
+ <OverflowMenuItem
379
+ onClick={() => handleSwapCompartment(rowData.patientUuid, rowData.bedId)}
380
+ itemText={t('compartmentSwap', 'Compartment swap')}
381
+ />
382
+ <OverflowMenuItem
383
+ onClick={() => handleDispose(rowData.patientUuid, rowData.bedId)}
384
+ itemText={t('disposeForm', 'Dispose')}
385
+ />
386
+ <OverflowMenuItem
387
+ onClick={() => handleDischarge(rowData.patientUuid, rowData.bedId)}
388
+ itemText={t('dischargeForm', 'Discharge')}
389
+ />
390
+ </OverflowMenu>
391
+ )}
392
+ </TableCell>
393
+ );
394
+ }
395
+ return <TableCell key={cell.id}>{rowData[cellKey] || '-'}</TableCell>;
396
+ })}
397
+ </TableRow>
398
+ );
399
+ })}
400
+ </TableBody>
401
+ </Table>
402
+ </TableContainer>
403
+ )}
404
+ </DataTable>
405
+
406
+ {paginated && !isLoading && totalCount > 0 && (
407
+ <Pagination
408
+ page={currentPage}
409
+ pageSize={currPageSize}
410
+ pageSizes={pageSizes}
411
+ totalItems={totalCount}
412
+ size={'sm'}
413
+ onChange={handlePaginationChange}
414
+ />
415
+ )}
416
+ </div>
417
+ );
418
+ };
419
+
420
+ export default AdmittedBedLineListView;
@@ -0,0 +1,224 @@
1
+ // AwaitingBedLineListView.tsx
2
+ import React, { useState, useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import {
5
+ DataTable,
6
+ InlineLoading,
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableContainer,
11
+ TableHead,
12
+ TableHeader,
13
+ TableRow,
14
+ Button,
15
+ Pagination,
16
+ OverflowMenu,
17
+ OverflowMenuItem,
18
+ DataTableSkeleton,
19
+ } from '@carbon/react';
20
+ import styles from '../bed-linelist-view.scss';
21
+ import { formatDateTime } from '../../utils/utils';
22
+ import { type MortuaryLocationResponse, type MortuaryPatient } from '../../types';
23
+ import { launchWorkspace } from '@openmrs/esm-framework';
24
+ import { useAwaitingPatients } from '../../home/home.resource';
25
+
26
+ interface AwaitingBedLineListViewProps {
27
+ awaitingQueueDeceasedPatients: Array<MortuaryPatient>;
28
+ mortuaryLocation: MortuaryLocationResponse;
29
+ isLoading: boolean;
30
+ paginated?: boolean;
31
+ initialPageSize?: number;
32
+ pageSizes?: number[];
33
+ mutated?: () => void;
34
+ }
35
+
36
+ const AwaitingBedLineListView: React.FC<AwaitingBedLineListViewProps> = ({
37
+ awaitingQueueDeceasedPatients,
38
+ isLoading,
39
+ mortuaryLocation,
40
+ paginated = true,
41
+ initialPageSize = 10,
42
+ pageSizes = [10, 20, 30, 40, 50],
43
+ mutated,
44
+ }) => {
45
+ const { t } = useTranslation();
46
+
47
+ const [currentPage, setCurrentPage] = useState(1);
48
+ const [currPageSize, setCurrPageSize] = useState(initialPageSize);
49
+
50
+ const trulyAwaitingPatients = useAwaitingPatients(awaitingQueueDeceasedPatients);
51
+
52
+ const headers = [
53
+ { key: 'admissionDate', header: t('dateQueued', 'Date Queued') },
54
+ { key: 'idNumber', header: t('idNumber', 'ID Number') },
55
+ { key: 'name', header: t('name', 'Name') },
56
+ { key: 'gender', header: t('gender', 'Gender') },
57
+ { key: 'age', header: t('age', 'Age') },
58
+ { key: 'bedNumber', header: t('compartmentNumber', 'Compartment Number') },
59
+ { key: 'daysAdmitted', header: t('durationOnWard', 'Days In Queue') },
60
+ { key: 'action', header: t('action', 'Action') },
61
+ ];
62
+
63
+ const calculateDaysInQueue = (dateOfDeath: string): number => {
64
+ if (!dateOfDeath) {
65
+ return 0;
66
+ }
67
+ const deathDate = new Date(dateOfDeath);
68
+ const currentDate = new Date();
69
+ const timeDiff = currentDate.getTime() - deathDate.getTime();
70
+ return Math.floor(timeDiff / (1000 * 3600 * 24));
71
+ };
72
+
73
+ const allRows = useMemo(() => {
74
+ if (!trulyAwaitingPatients || trulyAwaitingPatients.length === 0) {
75
+ return [];
76
+ }
77
+
78
+ const rows = trulyAwaitingPatients.map((mortuaryPatient, index) => {
79
+ const patientUuid = mortuaryPatient?.person?.person?.uuid || `patient-${index}`;
80
+ const patientName = mortuaryPatient?.person?.person?.display || '-';
81
+ const gender = mortuaryPatient?.person?.person?.gender || '-';
82
+ const age = mortuaryPatient?.person?.person?.age || '-';
83
+ const dateOfDeath = mortuaryPatient?.person?.person?.deathDate;
84
+ const daysInQueue = calculateDaysInQueue(dateOfDeath);
85
+
86
+ return {
87
+ id: patientUuid,
88
+ admissionDate: formatDateTime(dateOfDeath),
89
+ idNumber:
90
+ mortuaryPatient?.person?.identifiers
91
+ ?.find((id) => id.display?.includes('OpenMRS ID'))
92
+ ?.display?.split('=')?.[1]
93
+ ?.trim() || '-',
94
+ name: patientName,
95
+ gender: gender,
96
+ age: age.toString(),
97
+ bedNumber: '-',
98
+ daysAdmitted: daysInQueue.toString(),
99
+ action: patientUuid,
100
+ };
101
+ });
102
+
103
+ return rows;
104
+ }, [trulyAwaitingPatients]);
105
+
106
+ const totalCount = allRows.length;
107
+ const startIndex = (currentPage - 1) * currPageSize;
108
+ const endIndex = startIndex + currPageSize;
109
+ const paginatedRows = paginated ? allRows.slice(startIndex, endIndex) : allRows;
110
+
111
+ const handleAdmit = (patientData: MortuaryPatient) => {
112
+ launchWorkspace('admit-deceased-person-form', {
113
+ workspaceTitle: t('admissionForm', 'Admission form'),
114
+ patientData,
115
+ mortuaryLocation,
116
+ mutated,
117
+ });
118
+ };
119
+
120
+ const handleCancel = (patientUuid: string, patientName: string) => {
121
+ // TODO: Implement cancel functionality
122
+ };
123
+
124
+ const goTo = (page: number) => {
125
+ setCurrentPage(page);
126
+ };
127
+
128
+ const handlePaginationChange = ({ page: newPage, pageSize }: { page: number; pageSize: number }) => {
129
+ if (newPage !== currentPage) {
130
+ goTo(newPage);
131
+ }
132
+ if (pageSize !== currPageSize) {
133
+ setCurrPageSize(pageSize);
134
+ setCurrentPage(1);
135
+ }
136
+ };
137
+
138
+ if (isLoading) {
139
+ return (
140
+ <div className={styles.loadingContainer}>
141
+ <DataTableSkeleton columnCount={headers.length} rowCount={5} />
142
+ </div>
143
+ );
144
+ }
145
+
146
+ if (!trulyAwaitingPatients || trulyAwaitingPatients.length === 0) {
147
+ return (
148
+ <div className={styles.emptyState}>
149
+ <p>{t('noDeceasedPatients', 'No deceased patients awaiting admission')}</p>
150
+ </div>
151
+ );
152
+ }
153
+
154
+ return (
155
+ <div className={styles.bedLayoutWrapper}>
156
+ <DataTable rows={paginatedRows} headers={headers} isSortable useZebraStyles>
157
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps, getCellProps }) => (
158
+ <TableContainer>
159
+ <Table {...getTableProps()} aria-label="deceased patients table">
160
+ <TableHead>
161
+ <TableRow>
162
+ {headers.map((header) => (
163
+ <TableHeader
164
+ key={header.key}
165
+ {...getHeaderProps({
166
+ header,
167
+ })}>
168
+ {header.header}
169
+ </TableHeader>
170
+ ))}
171
+ </TableRow>
172
+ </TableHead>
173
+ <TableBody>
174
+ {rows.map((row) => {
175
+ const patientData = trulyAwaitingPatients.find((patient) => patient?.person?.person?.uuid === row.id);
176
+ const patientName = patientData?.person?.person?.display || '';
177
+
178
+ return (
179
+ <TableRow key={row.id} {...getRowProps({ row })}>
180
+ {row.cells.map((cell) => (
181
+ <TableCell key={cell.id} {...getCellProps({ cell })}>
182
+ {cell.info.header === 'action' ? (
183
+ <div className={styles.actionButtons}>
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>
200
+ ))}
201
+ </TableRow>
202
+ );
203
+ })}
204
+ </TableBody>
205
+ </Table>
206
+ </TableContainer>
207
+ )}
208
+ </DataTable>
209
+
210
+ {paginated && !isLoading && totalCount > 0 && (
211
+ <Pagination
212
+ page={currentPage}
213
+ pageSize={currPageSize}
214
+ pageSizes={pageSizes}
215
+ totalItems={totalCount}
216
+ size={'sm'}
217
+ onChange={handlePaginationChange}
218
+ />
219
+ )}
220
+ </div>
221
+ );
222
+ };
223
+
224
+ export default AwaitingBedLineListView;
@@ -0,0 +1,5 @@
1
+ .actionButtons {
2
+ display: flex;
3
+ gap: 0.5rem;
4
+ align-items: center;
5
+ }