@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,14 +1,15 @@
1
- import React from 'react';
1
+ import React, { useState, useMemo } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { launchWorkspace, showModal, useConfig } from '@openmrs/esm-framework';
4
- import { DataTableSkeleton, InlineLoading, Pagination } from '@carbon/react';
3
+ import { showModal, useConfig, useLayoutType } from '@openmrs/esm-framework';
4
+ import { InlineLoading, Pagination, Search } from '@carbon/react';
5
5
  import styles from '../bed-layout.scss';
6
- import { Patient, type MortuaryLocationResponse } from '../../types';
6
+ import { Patient, type MortuaryLocationResponse, EnhancedPatient } from '../../types';
7
7
  import { ConfigObject } from '../../config-schema';
8
- import { mutate as mutateSWR } from 'swr';
9
8
  import usePatients, { useMortuaryDischargeEncounter } from './discharged-bed-layout.resource';
10
9
  import BedCard from '../../bed/bed.component';
11
-
10
+ import EmptyMorgueAdmission from '../../empty-state/empty-morgue-admission.component';
11
+ import { transformDischargedPatient, extractPatientFromEnhanced } from '../../helpers/expression-helper';
12
+ import { PatientProvider } from '../../context/deceased-person-context';
12
13
  interface BedLayoutProps {
13
14
  AdmittedDeceasedPatient: MortuaryLocationResponse | null;
14
15
  isLoading: boolean;
@@ -17,15 +18,12 @@ interface BedLayoutProps {
17
18
  mutate?: () => void;
18
19
  }
19
20
 
20
- const DischargedBedLayout: React.FC<BedLayoutProps> = ({
21
- AdmittedDeceasedPatient,
22
- isLoading,
23
- onPrintGatePass,
24
- onPrintPostmortem,
25
- mutate,
26
- }) => {
21
+ const DischargedBedLayout: React.FC<BedLayoutProps> = ({ AdmittedDeceasedPatient, isLoading, onPrintGatePass }) => {
27
22
  const { t } = useTranslation();
28
23
  const { morgueDischargeEncounterTypeUuid } = useConfig<ConfigObject>();
24
+ const [searchTerm, setSearchTerm] = useState('');
25
+ const isTablet = useLayoutType() === 'tablet';
26
+ const controlSize = isTablet ? 'md' : 'sm';
29
27
 
30
28
  const {
31
29
  dischargedPatientUuids,
@@ -45,6 +43,32 @@ const DischargedBedLayout: React.FC<BedLayoutProps> = ({
45
43
  error: patientsError,
46
44
  } = usePatients(dischargedPatientUuids || []);
47
45
 
46
+ const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
47
+ setSearchTerm(event.target.value);
48
+ };
49
+
50
+ const filteredPatients = useMemo(() => {
51
+ if (!dischargedPatients || !searchTerm.trim()) {
52
+ return dischargedPatients || [];
53
+ }
54
+
55
+ const lowerSearchTerm = searchTerm.toLowerCase().trim();
56
+
57
+ return dischargedPatients.filter((patient) => {
58
+ const patientName = patient?.person?.display?.toLowerCase() || '';
59
+ const gender = patient?.person?.gender?.toLowerCase() || '';
60
+ const patientId = patient?.uuid?.toLowerCase() || '';
61
+ const causeOfDeath = patient?.person?.causeOfDeath?.display?.toLowerCase() || '';
62
+
63
+ return (
64
+ patientName.includes(lowerSearchTerm) ||
65
+ gender.includes(lowerSearchTerm) ||
66
+ patientId.includes(lowerSearchTerm) ||
67
+ causeOfDeath.includes(lowerSearchTerm)
68
+ );
69
+ });
70
+ }, [dischargedPatients, searchTerm]);
71
+
48
72
  const getEncounterDateForPatient = (patientUuid: string): string | null => {
49
73
  if (!encounters || encounters.length === 0) {
50
74
  return null;
@@ -55,29 +79,30 @@ const DischargedBedLayout: React.FC<BedLayoutProps> = ({
55
79
  return patientEncounter?.encounterDateTime || null;
56
80
  };
57
81
 
58
- const handlePrintGatePass = (patient: Patient, encounterDate?: string) => {
59
- if (onPrintGatePass) {
60
- onPrintGatePass(patient, encounterDate);
82
+ const handlePrintGatePass = (patient: EnhancedPatient | Patient, encounterDate?: string) => {
83
+ let originalPatient: Patient | null = null;
84
+
85
+ if ('originalPatient' in patient || 'originalMortuaryPatient' in patient) {
86
+ originalPatient = extractPatientFromEnhanced(patient as EnhancedPatient);
61
87
  } else {
88
+ originalPatient = patient as Patient;
89
+ }
90
+
91
+ if (onPrintGatePass && originalPatient) {
92
+ onPrintGatePass(originalPatient, encounterDate);
93
+ } else if (originalPatient) {
62
94
  const dispose = showModal('print-confirmation-modal', {
63
95
  onClose: () => dispose(),
64
- patient: patient,
96
+ patient: originalPatient,
65
97
  encounterDate: encounterDate,
66
98
  });
67
99
  }
68
100
  };
69
101
 
70
- const handlePrintPostmortem = (patientUuid: string) => {
71
- if (onPrintPostmortem) {
72
- onPrintPostmortem(patientUuid);
73
- }
74
- };
75
-
76
- const handlePageSizeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
77
- const newPageSize = parseInt(event.target.value, 10);
78
- setCurrPageSize(newPageSize);
79
- // Reset to first page when changing page size
80
- goTo(1);
102
+ const patientContextValue = {
103
+ mortuaryLocation: AdmittedDeceasedPatient,
104
+ isLoading: isLoading || encountersLoading || patientsLoading,
105
+ onPrintGatePass: handlePrintGatePass,
81
106
  };
82
107
 
83
108
  if (isLoading || encountersLoading || patientsLoading) {
@@ -98,57 +123,90 @@ const DischargedBedLayout: React.FC<BedLayoutProps> = ({
98
123
 
99
124
  if (!dischargedPatients || dischargedPatients.length === 0) {
100
125
  return (
101
- <div className={styles.loadingContainer}>
102
- <DataTableSkeleton columnCount={5} rowCount={5} zebra />
103
- </div>
126
+ <EmptyMorgueAdmission
127
+ title={t('noDischargedPatient', 'No deceased patients currently discharged')}
128
+ subTitle={t(
129
+ 'noDischargedPatientsDescription',
130
+ 'There are no discharged deceased patients to display at this time.',
131
+ )}
132
+ />
104
133
  );
105
134
  }
106
135
 
107
- return (
108
- <div className={styles.bedLayoutWrapper}>
109
- <div className={styles.bedLayoutContainer}>
110
- {dischargedPatients.map((patient) => {
111
- const patientUuid = patient?.uuid;
112
- const patientName = patient?.person?.display;
113
- const gender = patient?.person?.gender;
114
- const age = patient?.person?.age;
115
- const causeOfDeath = patient?.person?.causeOfDeath?.display;
116
- const dateOfDeath = patient?.person?.deathDate;
117
- const encounterDate = getEncounterDateForPatient(patientUuid);
118
-
119
- return (
120
- <BedCard
121
- key={patientUuid}
122
- patientName={patientName}
123
- gender={gender}
124
- age={age}
125
- causeOfDeath={causeOfDeath}
126
- dateOfDeath={dateOfDeath}
127
- patientUuid={patientUuid}
128
- onPrintGatePass={() => handlePrintGatePass(patient, encounterDate)}
129
- isDischarged={true}
130
- />
131
- );
132
- })}
133
- </div>
136
+ const patientsToShow = filteredPatients;
137
+
138
+ if (searchTerm.trim() && patientsToShow.length === 0) {
139
+ return (
140
+ <>
141
+ <div className={styles.searchContainer}>
142
+ <Search
143
+ labelText={t('searchDeceasedPatients', 'Search deceased patients')}
144
+ placeholder={t(
145
+ 'searchPatientsPlaceholder',
146
+ 'Search by name, ID number, gender, compartment, or bed type...',
147
+ )}
148
+ value={searchTerm}
149
+ onChange={handleSearchChange}
150
+ size={controlSize}
151
+ />
152
+ </div>
153
+ <EmptyMorgueAdmission
154
+ title={t('noMatchingDischargedPatients', 'No matching discharged patients found')}
155
+ subTitle={t(
156
+ 'noMatchingDischargedPatientsDescription',
157
+ 'Try adjusting your search terms to find discharged patients.',
158
+ )}
159
+ />
160
+ </>
161
+ );
162
+ }
134
163
 
135
- <div className={styles.paginationFooter}>
136
- <Pagination
137
- page={currentPage || 1}
138
- totalItems={totalCount || 0}
139
- pageSize={currPageSize}
140
- pageSizes={[10, 20, 50, 100]}
141
- onChange={({ page, pageSize }: { page: number; pageSize: number }) => {
142
- if (pageSize !== currPageSize) {
143
- setCurrPageSize(pageSize);
144
- goTo(1);
145
- } else {
146
- goTo(page);
147
- }
148
- }}
164
+ return (
165
+ <PatientProvider value={patientContextValue}>
166
+ <div className={styles.searchContainer}>
167
+ <Search
168
+ labelText={t('searchDeceasedPatients', 'Search deceased patients')}
169
+ placeholder={t('searchPatientsPlaceholder', 'Search by name, ID number, gender, compartment, or bed type...')}
170
+ value={searchTerm}
171
+ onChange={handleSearchChange}
172
+ size="sm"
149
173
  />
150
174
  </div>
151
- </div>
175
+ <div className={styles.bedLayoutWrapper}>
176
+ <div className={styles.bedLayoutContainer}>
177
+ {patientsToShow.map((patient) => {
178
+ const encounterDate = getEncounterDateForPatient(patient.uuid);
179
+
180
+ return (
181
+ <BedCard
182
+ key={patient.uuid}
183
+ patient={transformDischargedPatient(patient, encounterDate)}
184
+ showActions={{
185
+ printGatePass: true,
186
+ }}
187
+ />
188
+ );
189
+ })}
190
+ </div>
191
+
192
+ <div className={styles.paginationFooter}>
193
+ <Pagination
194
+ page={currentPage || 1}
195
+ totalItems={totalCount || 0}
196
+ pageSize={currPageSize}
197
+ pageSizes={[10, 20, 50, 100]}
198
+ onChange={({ page, pageSize }: { page: number; pageSize: number }) => {
199
+ if (pageSize !== currPageSize) {
200
+ setCurrPageSize(pageSize);
201
+ goTo(1);
202
+ } else {
203
+ goTo(page);
204
+ }
205
+ }}
206
+ />
207
+ </div>
208
+ </div>
209
+ </PatientProvider>
152
210
  );
153
211
  };
154
212
 
@@ -14,14 +14,15 @@ import {
14
14
  OverflowMenuItem,
15
15
  Tag,
16
16
  DataTableSkeleton,
17
+ Search,
17
18
  } from '@carbon/react';
18
- import { launchWorkspace, navigate, useConfig, useVisit } from '@openmrs/esm-framework';
19
+ import { launchWorkspace, navigate, useConfig, useLayoutType, useVisit } from '@openmrs/esm-framework';
19
20
  import styles from '../bed-linelist-view.scss';
20
21
  import { convertDateToDays, formatDateTime } from '../../utils/utils';
21
22
  import { Patient, Person, type MortuaryLocationResponse } from '../../types';
22
23
  import { ConfigObject } from '../../config-schema';
23
24
  import { mutate as mutateSWR } from 'swr';
24
- import { EmptyState } from '@openmrs/esm-patient-common-lib/src';
25
+ import EmptyMorgueAdmission from '../../empty-state/empty-morgue-admission.component';
25
26
 
26
27
  interface AdmittedBedLineListViewProps {
27
28
  AdmittedDeceasedPatient: MortuaryLocationResponse | null;
@@ -50,9 +51,12 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
50
51
  }) => {
51
52
  const { t } = useTranslation();
52
53
  const { autopsyFormUuid } = useConfig<ConfigObject>();
54
+ const isTablet = useLayoutType() === 'tablet';
55
+ const controlSize = isTablet ? 'md' : 'sm';
53
56
 
54
57
  const [currentPage, setCurrentPage] = useState(1);
55
58
  const [currPageSize, setCurrPageSize] = useState(initialPageSize);
59
+ const [searchTerm, setSearchTerm] = useState('');
56
60
 
57
61
  const DaysInMortuary = ({ patientUuid }: { patientUuid: string }) => {
58
62
  const { activeVisit } = useVisit(patientUuid);
@@ -84,7 +88,9 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
84
88
  { key: 'action', header: t('action', 'Action') },
85
89
  ];
86
90
 
87
- const handlePostmortem = (patientUuid: string) => {
91
+ const handlePostmortem = (patientUuid: string, bedInfo?: { bedNumber: string; bedId: number }) => {
92
+ const hasBedInfo = bedInfo?.bedNumber && bedInfo?.bedId;
93
+
88
94
  if (onPostmortem) {
89
95
  onPostmortem(patientUuid);
90
96
  } else {
@@ -100,8 +106,12 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
100
106
  },
101
107
  });
102
108
  }
109
+ const base = `${window.getOpenmrsSpaBase()}home/morgue/patient/${patientUuid}`;
110
+ const to = hasBedInfo
111
+ ? `${base}/compartment/${bedInfo.bedNumber}/${bedInfo.bedId}/mortuary-chart`
112
+ : `${base}/mortuary-chart`;
113
+ navigate({ to });
103
114
  };
104
-
105
115
  const handleDischarge = (patientUuid: string, bedId: number) => {
106
116
  if (onDischarge) {
107
117
  onDischarge(patientUuid);
@@ -129,19 +139,6 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
129
139
  }
130
140
  };
131
141
 
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
142
  const calculateDaysAdmitted = (dateOfDeath: string): number => {
146
143
  if (!dateOfDeath) {
147
144
  return 0;
@@ -226,6 +223,7 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
226
223
  daysAdmitted: '-',
227
224
  isEmpty: true,
228
225
  bedId,
226
+ searchableText: `${bedNumber} ${bedType}`.toLowerCase(),
229
227
  });
230
228
  } else {
231
229
  for (const patient of patients) {
@@ -236,6 +234,7 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
236
234
  const causeOfDeath = patient.person?.causeOfDeath?.display || t('unknown', 'Unknown');
237
235
  const dateOfDeath = patient.person?.deathDate;
238
236
  const daysAdmitted = calculateDaysAdmitted(dateOfDeath).toString();
237
+ const idNumber = getIdNumber(patient);
239
238
 
240
239
  rows.push({
241
240
  id: `${bedUuid}-${patientUuid}`,
@@ -244,7 +243,7 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
244
243
  bedType,
245
244
  status: bedStatus,
246
245
  patientName,
247
- idNumber: getIdNumber(patient),
246
+ idNumber,
248
247
  gender,
249
248
  age,
250
249
  dateOfDeath: formatDateTime(dateOfDeath),
@@ -255,6 +254,7 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
255
254
  bedUuid,
256
255
  bedId,
257
256
  personUuid: patient.person?.uuid || '',
257
+ searchableText: `${patientName} ${idNumber} ${gender} ${bedNumber} ${bedType}`.toLowerCase(),
258
258
  });
259
259
  }
260
260
  }
@@ -263,6 +263,32 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
263
263
  return rows;
264
264
  }, [getCompartmentShare, AdmittedDeceasedPatient, t, isLoading]);
265
265
 
266
+ const filteredRows = useMemo(() => {
267
+ if (!searchTerm.trim()) {
268
+ return allRows;
269
+ }
270
+
271
+ const searchLower = searchTerm.toLowerCase().trim();
272
+ return allRows.filter(
273
+ (row) =>
274
+ row.searchableText.includes(searchLower) ||
275
+ row.patientName.toLowerCase().includes(searchLower) ||
276
+ row.idNumber.toLowerCase().includes(searchLower) ||
277
+ row.gender.toLowerCase().includes(searchLower) ||
278
+ row.bedNumber?.toString().includes(searchLower) ||
279
+ row.bedType.toLowerCase().includes(searchLower),
280
+ );
281
+ }, [allRows, searchTerm]);
282
+
283
+ const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
284
+ setSearchTerm(event.target.value);
285
+ setCurrentPage(1);
286
+ };
287
+
288
+ const hasSearchTerm = searchTerm.trim().length > 0;
289
+ const hasNoSearchResults = hasSearchTerm && filteredRows.length === 0;
290
+ const totalCount = filteredRows.length;
291
+
266
292
  if (isLoading) {
267
293
  return (
268
294
  <div className={styles.loadingContainer}>
@@ -274,18 +300,20 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
274
300
  if (!AdmittedDeceasedPatient) {
275
301
  return (
276
302
  <div className={styles.loadingContainer}>
277
- <EmptyState
278
- headerTitle={t('noAdmittedPatients', 'No admitted patients found')}
279
- displayText={t('noAdmittedPatientsDescription', 'There are currently no admitted patients to display.')}
303
+ <EmptyMorgueAdmission
304
+ title={t('noAdmittedPatient', 'No deceased patients currently admitted')}
305
+ subTitle={t(
306
+ 'noAdmittedPatientsDescription',
307
+ 'There are no admitted deceased patients to display at this time.',
308
+ )}
280
309
  />
281
310
  </div>
282
311
  );
283
312
  }
284
313
 
285
- const totalCount = allRows.length;
286
314
  const startIndex = (currentPage - 1) * currPageSize;
287
315
  const endIndex = startIndex + currPageSize;
288
- const paginatedRows = paginated ? allRows.slice(startIndex, endIndex) : allRows;
316
+ const paginatedRows = paginated ? filteredRows.slice(startIndex, endIndex) : filteredRows;
289
317
 
290
318
  const goTo = (page: number) => setCurrentPage(page);
291
319
 
@@ -299,120 +327,140 @@ const AdmittedBedLineListView: React.FC<AdmittedBedLineListViewProps> = ({
299
327
  }
300
328
  };
301
329
 
330
+ const NoSearchResults = () => (
331
+ <div className={styles.emptyState}>
332
+ <EmptyMorgueAdmission
333
+ title={t('noSearchResults', 'We couldn’t find anything')}
334
+ subTitle={t('tryAgain', 'Try adjusting your search {{searchTerm}} and try again', { searchTerm })}
335
+ />
336
+ </div>
337
+ );
338
+
302
339
  return (
303
340
  <div className={styles.bedLayoutWrapper}>
304
- <DataTable rows={paginatedRows} headers={headers} isSortable useZebraStyles>
305
- {({ rows, headers, getHeaderProps, getRowProps, getTableProps, getCellProps }) => (
306
- <TableContainer>
307
- <Table {...getTableProps()} aria-label="mortuary beds table">
308
- <TableHead>
309
- <TableRow>
310
- {headers.map((header) => (
311
- <TableHeader key={header.key} {...getHeaderProps({ header })}>
312
- {header.header}
313
- </TableHeader>
314
- ))}
315
- </TableRow>
316
- </TableHead>
317
- <TableBody>
318
- {rows.map((row) => {
319
- const rowData = allRows.find((r) => r.id === row.id);
320
- if (!rowData) {
321
- return null;
322
- }
323
-
324
- return (
325
- <TableRow key={row.id} {...getRowProps({ row })}>
326
- {row.cells.map((cell) => {
327
- const cellKey = cell.info.header as keyof typeof rowData;
328
-
329
- if (cell.info.header === 'daysAdmitted' && !rowData.isEmpty) {
330
- return (
331
- <TableCell key={cell.id}>
332
- <DaysInMortuary patientUuid={rowData.patientUuid} />
333
- </TableCell>
334
- );
335
- }
336
-
337
- if (cell.info.header === 'admissionDate' && !rowData.isEmpty) {
338
- return (
339
- <TableCell key={cell.id}>
340
- <AdmissionDate patientUuid={rowData.patientUuid} />
341
- </TableCell>
342
- );
343
- }
344
-
345
- if (cell.info.header === 'status') {
346
- return (
347
- <TableCell key={cell.id}>
348
- <Tag type={rowData.status === 'AVAILABLE' ? 'green' : 'red'} size="sm">
349
- {rowData.status === 'AVAILABLE'
350
- ? t('available', 'Available')
351
- : t('occupied', 'Occupied')}
352
- </Tag>
353
- </TableCell>
354
- );
355
- }
356
-
357
- if (cell.info.header === 'action') {
358
- return (
359
- <TableCell key={cell.id}>
360
- {!rowData.isEmpty && (
361
- <OverflowMenu flipped>
362
- <OverflowMenuItem
363
- onClick={() => {
364
- const hasBedInfo = rowData.bedNumber && rowData.bedId;
365
- const base = `${window.getOpenmrsSpaBase()}home/morgue/patient/${
366
- rowData.patientUuid
367
- }`;
368
- const to = hasBedInfo
369
- ? `${base}/compartment/${rowData.bedNumber}/${rowData.bedId}/mortuary-chart`
370
- : `${base}/mortuary-chart`;
371
- navigate({ to });
372
- }}
373
- itemText={t('viewDetails', 'View details')}
374
- />
375
- <OverflowMenuItem
376
- onClick={() => handlePostmortem(rowData.patientUuid)}
377
- itemText={t('postmortemForm', 'Postmortem')}
378
- />
379
- <OverflowMenuItem
380
- onClick={() => handleSwapCompartment(rowData.patientUuid, rowData.bedId)}
381
- itemText={t('compartmentSwap', 'Compartment swap')}
382
- />
383
- <OverflowMenuItem
384
- onClick={() => handleDispose(rowData.patientUuid, rowData.bedId)}
385
- itemText={t('disposeForm', 'Dispose')}
386
- />
387
- <OverflowMenuItem
388
- onClick={() => handleDischarge(rowData.patientUuid, rowData.bedId)}
389
- itemText={t('dischargeForm', 'Discharge')}
390
- />
391
- </OverflowMenu>
392
- )}
393
- </TableCell>
394
- );
395
- }
396
- return <TableCell key={cell.id}>{rowData[cellKey] || '-'}</TableCell>;
397
- })}
398
- </TableRow>
399
- );
400
- })}
401
- </TableBody>
402
- </Table>
403
- </TableContainer>
404
- )}
405
- </DataTable>
406
-
407
- {paginated && !isLoading && totalCount > 0 && (
408
- <Pagination
409
- page={currentPage}
410
- pageSize={currPageSize}
411
- pageSizes={pageSizes}
412
- totalItems={totalCount}
413
- size={'sm'}
414
- onChange={handlePaginationChange}
341
+ <div className={styles.searchContainer}>
342
+ <Search
343
+ labelText={t('noSearchDeceasedPatients', 'Search deceased patients')}
344
+ placeholder={t('searchPatientsPlaceholder', 'Search by name, ID number, gender, compartment, or bed type...')}
345
+ value={searchTerm}
346
+ onChange={handleSearchChange}
347
+ size={controlSize}
415
348
  />
349
+ </div>
350
+ {hasNoSearchResults ? (
351
+ <NoSearchResults />
352
+ ) : (
353
+ <>
354
+ <DataTable rows={paginatedRows} headers={headers} isSortable useZebraStyles>
355
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => (
356
+ <TableContainer>
357
+ <Table {...getTableProps()} aria-label="mortuary beds table">
358
+ <TableHead>
359
+ <TableRow>
360
+ {headers.map((header) => (
361
+ <TableHeader key={header.key} {...getHeaderProps({ header })}>
362
+ {header.header}
363
+ </TableHeader>
364
+ ))}
365
+ </TableRow>
366
+ </TableHead>
367
+ <TableBody>
368
+ {rows.map((row) => {
369
+ const rowData = allRows.find((r) => r.id === row.id);
370
+ if (!rowData) {
371
+ return null;
372
+ }
373
+
374
+ return (
375
+ <TableRow key={row.id} {...getRowProps({ row })}>
376
+ {row.cells.map((cell) => {
377
+ const cellKey = cell.info.header as keyof typeof rowData;
378
+
379
+ if (cell.info.header === 'daysAdmitted' && !rowData.isEmpty) {
380
+ return (
381
+ <TableCell key={cell.id}>
382
+ <DaysInMortuary patientUuid={rowData.patientUuid} />
383
+ </TableCell>
384
+ );
385
+ }
386
+
387
+ if (cell.info.header === 'admissionDate' && !rowData.isEmpty) {
388
+ return (
389
+ <TableCell key={cell.id}>
390
+ <AdmissionDate patientUuid={rowData.patientUuid} />
391
+ </TableCell>
392
+ );
393
+ }
394
+
395
+ if (cell.info.header === 'status') {
396
+ return (
397
+ <TableCell key={cell.id}>
398
+ <Tag type={rowData.status === 'AVAILABLE' ? 'green' : 'red'} size="sm">
399
+ {rowData.status === 'AVAILABLE'
400
+ ? t('available', 'Available')
401
+ : t('occupied', 'Occupied')}
402
+ </Tag>
403
+ </TableCell>
404
+ );
405
+ }
406
+
407
+ if (cell.info.header === 'action') {
408
+ return (
409
+ <TableCell key={cell.id}>
410
+ {!rowData.isEmpty && (
411
+ <OverflowMenu flipped>
412
+ <OverflowMenuItem
413
+ onClick={() => {
414
+ const hasBedInfo = rowData.bedNumber && rowData.bedId;
415
+ const base = `${window.getOpenmrsSpaBase()}home/morgue/patient/${
416
+ rowData.patientUuid
417
+ }`;
418
+ const to = hasBedInfo
419
+ ? `${base}/compartment/${rowData.bedNumber}/${rowData.bedId}/mortuary-chart`
420
+ : `${base}/mortuary-chart`;
421
+ navigate({ to });
422
+ }}
423
+ itemText={t('viewDetails', 'View details')}
424
+ />
425
+ <OverflowMenuItem
426
+ onClick={() => handlePostmortem(rowData.patientUuid)}
427
+ itemText={t('postmortemForm', 'Postmortem')}
428
+ />
429
+ <OverflowMenuItem
430
+ onClick={() => handleSwapCompartment(rowData.patientUuid, rowData.bedId)}
431
+ itemText={t('compartmentSwap', 'Compartment swap')}
432
+ />
433
+ <OverflowMenuItem
434
+ onClick={() => handleDischarge(rowData.patientUuid, rowData.bedId)}
435
+ itemText={t('dischargeForm', 'Discharge')}
436
+ />
437
+ </OverflowMenu>
438
+ )}
439
+ </TableCell>
440
+ );
441
+ }
442
+ return <TableCell key={cell.id}>{rowData[cellKey] || '-'}</TableCell>;
443
+ })}
444
+ </TableRow>
445
+ );
446
+ })}
447
+ </TableBody>
448
+ </Table>
449
+ </TableContainer>
450
+ )}
451
+ </DataTable>
452
+
453
+ {paginated && !isLoading && totalCount > 0 && (
454
+ <Pagination
455
+ page={currentPage}
456
+ pageSize={currPageSize}
457
+ pageSizes={pageSizes}
458
+ totalItems={totalCount}
459
+ size={'sm'}
460
+ onChange={handlePaginationChange}
461
+ />
462
+ )}
463
+ </>
416
464
  )}
417
465
  </div>
418
466
  );