@kenyaemr/esm-morgue-app 5.4.2-pre.2344 → 5.4.2-pre.2349

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 (122) hide show
  1. package/.turbo/turbo-build.log +21 -26
  2. package/dist/162.js +2 -0
  3. package/dist/162.js.map +1 -0
  4. package/dist/197.js +1 -1
  5. package/dist/221.js +1 -1
  6. package/dist/221.js.map +1 -1
  7. package/dist/293.js +1 -1
  8. package/dist/294.js +1 -1
  9. package/dist/300.js +1 -1
  10. package/dist/351.js +1 -0
  11. package/dist/351.js.map +1 -0
  12. package/dist/38.js +1 -1
  13. package/dist/38.js.map +1 -1
  14. package/dist/404.js +1 -0
  15. package/dist/404.js.map +1 -0
  16. package/dist/441.js +1 -0
  17. package/dist/441.js.map +1 -0
  18. package/dist/467.js +1 -1
  19. package/dist/467.js.map +1 -1
  20. package/dist/500.js +1 -0
  21. package/dist/500.js.map +1 -0
  22. package/dist/570.js +1 -0
  23. package/dist/570.js.map +1 -0
  24. package/dist/608.js +2 -0
  25. package/dist/608.js.map +1 -0
  26. package/dist/611.js +2 -0
  27. package/dist/611.js.map +1 -0
  28. package/dist/632.js +2 -1
  29. package/dist/632.js.map +1 -1
  30. package/dist/653.js +1 -1
  31. package/dist/653.js.map +1 -1
  32. package/dist/657.js +1 -0
  33. package/dist/657.js.map +1 -0
  34. package/dist/805.js +1 -1
  35. package/dist/805.js.map +1 -1
  36. package/dist/814.js +2 -0
  37. package/dist/814.js.LICENSE.txt +5 -0
  38. package/dist/814.js.map +1 -0
  39. package/dist/824.js +1 -1
  40. package/dist/824.js.map +1 -1
  41. package/dist/845.js +1 -0
  42. package/dist/845.js.map +1 -0
  43. package/dist/888.js +1 -0
  44. package/dist/888.js.map +1 -0
  45. package/dist/899.js +1 -0
  46. package/dist/899.js.map +1 -0
  47. package/dist/918.js +1 -1
  48. package/dist/918.js.map +1 -1
  49. package/dist/990.js +1 -0
  50. package/dist/990.js.map +1 -0
  51. package/dist/kenyaemr-esm-morgue-app.js +1 -1
  52. package/dist/kenyaemr-esm-morgue-app.js.buildmanifest.json +258 -184
  53. package/dist/kenyaemr-esm-morgue-app.js.map +1 -1
  54. package/dist/main.js +1 -1
  55. package/dist/main.js.LICENSE.txt +0 -6
  56. package/dist/main.js.map +1 -1
  57. package/dist/routes.json +1 -1
  58. package/package.json +1 -1
  59. package/src/bed/bed.component.tsx +63 -134
  60. package/src/bed/components/deceased-patient-card-header.component.tsx +73 -0
  61. package/src/bed/components/deceased-patient-info.component.tsx +47 -0
  62. package/src/bed/components/deceased-patient-status-footer.component.tsx +43 -0
  63. package/src/bed-layout/admitted/admitted-bed-layout.component.tsx +175 -96
  64. package/src/bed-layout/awaiting/awaiting-bed-layout.component.tsx +103 -36
  65. package/src/bed-layout/bed-layout.scss +4 -0
  66. package/src/bed-layout/discharged/discharged-bed-layout.component.tsx +131 -73
  67. package/src/bed-linelist-view/admitted/admitted-bed-linelist-view.component.tsx +182 -134
  68. package/src/bed-linelist-view/awaiting/awaiting-bed-linelist-view.component.tsx +115 -71
  69. package/src/bed-linelist-view/discharged/discharged-bed-line-view.component.tsx +181 -109
  70. package/src/config-schema.ts +140 -4
  71. package/src/context/deceased-person-context.tsx +33 -0
  72. package/src/extension/actionButton.component.tsx +1 -1
  73. package/src/forms/admit-deceased-person-workspace/admit-deceased-person.resource.ts +84 -166
  74. package/src/forms/admit-deceased-person-workspace/admit-deceased-person.scss +14 -0
  75. package/src/forms/admit-deceased-person-workspace/admit-deceased-person.workspace.tsx +504 -334
  76. package/src/forms/discharge-deceased-person-workspace/discharge-body.resource.ts +0 -1
  77. package/src/forms/discharge-deceased-person-workspace/discharge-body.scss +15 -0
  78. package/src/forms/discharge-deceased-person-workspace/discharge-body.workspace.tsx +303 -244
  79. package/src/helpers/expression-helper.ts +122 -0
  80. package/src/home/home.component.tsx +23 -4
  81. package/src/index.ts +0 -2
  82. package/src/metrics/metrics-card.component.tsx +2 -2
  83. package/src/routes.json +0 -6
  84. package/src/schemas/index.ts +243 -51
  85. package/src/summary/summary.component.tsx +16 -9
  86. package/src/switcher/content-switcher.component.tsx +61 -35
  87. package/src/switcher/content-switcher.scss +13 -0
  88. package/src/types/index.ts +43 -1
  89. package/translations/am.json +16 -6
  90. package/translations/en.json +16 -6
  91. package/translations/sw.json +16 -6
  92. package/dist/347.js +0 -1
  93. package/dist/347.js.map +0 -1
  94. package/dist/373.js +0 -2
  95. package/dist/373.js.map +0 -1
  96. package/dist/398.js +0 -1
  97. package/dist/398.js.map +0 -1
  98. package/dist/410.js +0 -1
  99. package/dist/410.js.map +0 -1
  100. package/dist/429.js +0 -2
  101. package/dist/429.js.map +0 -1
  102. package/dist/579.js +0 -2
  103. package/dist/579.js.map +0 -1
  104. package/dist/619.js +0 -1
  105. package/dist/619.js.map +0 -1
  106. package/dist/633.js +0 -1
  107. package/dist/633.js.map +0 -1
  108. package/dist/712.js +0 -1
  109. package/dist/712.js.map +0 -1
  110. package/dist/713.js +0 -1
  111. package/dist/713.js.map +0 -1
  112. package/dist/723.js +0 -1
  113. package/dist/723.js.map +0 -1
  114. package/dist/989.js +0 -2
  115. package/dist/989.js.map +0 -1
  116. package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.resource.ts +0 -18
  117. package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.scss +0 -84
  118. package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.workspace.tsx +0 -505
  119. /package/dist/{989.js.LICENSE.txt → 162.js.LICENSE.txt} +0 -0
  120. /package/dist/{373.js.LICENSE.txt → 608.js.LICENSE.txt} +0 -0
  121. /package/dist/{429.js.LICENSE.txt → 611.js.LICENSE.txt} +0 -0
  122. /package/dist/{579.js.LICENSE.txt → 632.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 totalCount = allRows.length;
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 ? allRows.slice(startIndex, endIndex) : allRows;
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 = (patientUuid: string, patientName: string) => {
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
- <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>
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
- </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
- />
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 = (patientUuid: string): string | null => {
89
- if (!encounters || encounters.length === 0) {
90
- return null;
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
- return patientEncounter?.encounterDateTime || null;
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 = (patient: Patient, encounterDate?: string) => {
99
- if (onPrintGatePass) {
100
- onPrintGatePass(patient, encounterDate);
101
- } else {
102
- const dispose = showModal('print-confirmation-modal', {
103
- onClose: () => dispose(),
104
- patient: patient,
105
- encounterDate: encounterDate,
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 totalCount = allRows.length;
149
- const startIndex = (currentPage - 1) * currPageSize;
150
- const endIndex = startIndex + currPageSize;
151
- const paginatedRows = paginated ? allRows.slice(startIndex, endIndex) : allRows;
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 = ({ page: newPage, pageSize }: { page: number; pageSize: number }) => {
158
- if (newPage !== currentPage) {
159
- goTo(newPage);
160
- }
161
- if (pageSize !== currPageSize) {
162
- setCurrPageSize(pageSize);
163
- setCurrentPage(1);
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 (!dischargedPatients || dischargedPatients.length === 0) {
228
+ if (!hasPatients) {
189
229
  return (
190
230
  <div className={styles.emptyState}>
191
- <EmptyState
192
- headerTitle={t('noDischargedPatients', 'No discharged patients found')}
193
- displayText={t('noDischargedPatientsDescription', 'There are currently no discharged patients to display.')}
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
- <DataTable rows={paginatedRows} headers={headers} isSortable useZebraStyles>
202
- {({ rows, headers, getHeaderProps, getRowProps, getTableProps, getCellProps }) => (
203
- <TableContainer>
204
- <Table {...getTableProps()} aria-label="discharged patients table">
205
- <TableHead>
206
- <TableRow>
207
- {headers.map((header) => (
208
- <TableHeader
209
- key={header.key}
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
- return (
225
- <TableRow key={row.id} {...getRowProps({ row })}>
226
- {row.cells.map((cell) => (
227
- <TableCell key={cell.id} {...getCellProps({ cell })}>
228
- {cell.info.header === 'action' ? (
229
- <div className={styles.actionButtons}>
230
- <OverflowMenu renderIcon={Printer} flipped>
231
- <OverflowMenuItem
232
- onClick={() => handlePrintGatePass(patientData, encounterDate)}
233
- itemText={t('printGatePass', 'Gate Pass')}
234
- disabled={!patientData}
235
- />
236
- <ExtensionSlot
237
- name="print-post-mortem-overflow-menu-item-slot"
238
- state={{ patientUuid: row.id }}
239
- />
240
- </OverflowMenu>
241
- </div>
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
- </TableBody>
251
- </Table>
252
- </TableContainer>
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
- {paginated && !isLoadingData && totalCount > 0 && (
257
- <Pagination
258
- page={currentPage}
259
- pageSize={currPageSize}
260
- pageSizes={pageSizes}
261
- totalItems={totalCount}
262
- size={'sm'}
263
- onChange={handlePaginationChange}
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
  );