@kenyaemr/esm-morgue-app 5.4.1-pre.1848 → 5.4.1-pre.1850

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 (111) hide show
  1. package/.turbo/turbo-build.log +84 -21
  2. package/dist/160.js +1 -0
  3. package/dist/160.js.map +1 -0
  4. package/dist/195.js +1 -0
  5. package/dist/195.js.map +1 -0
  6. package/dist/240.js +1 -0
  7. package/dist/240.js.map +1 -0
  8. package/dist/282.js +1 -0
  9. package/dist/282.js.map +1 -0
  10. package/dist/300.js +1 -1
  11. package/dist/{561.js → 521.js} +1 -1
  12. package/dist/521.js.map +1 -0
  13. package/dist/527.js +1 -0
  14. package/dist/527.js.map +1 -0
  15. package/dist/596.js +1 -0
  16. package/dist/596.js.map +1 -0
  17. package/dist/652.js +1 -1
  18. package/dist/652.js.map +1 -1
  19. package/dist/659.js +2 -0
  20. package/dist/659.js.map +1 -0
  21. package/dist/675.js +2 -0
  22. package/dist/{485.js.map → 675.js.map} +1 -1
  23. package/dist/730.js +1 -0
  24. package/dist/730.js.map +1 -0
  25. package/dist/755.js +1 -0
  26. package/dist/755.js.map +1 -0
  27. package/dist/795.js +1 -0
  28. package/dist/795.js.map +1 -0
  29. package/dist/818.js +1 -0
  30. package/dist/818.js.map +1 -0
  31. package/dist/{942.js → 870.js} +1 -1
  32. package/dist/870.js.map +1 -0
  33. package/dist/909.js +2 -0
  34. package/dist/909.js.map +1 -0
  35. package/dist/926.js +1 -1
  36. package/dist/926.js.map +1 -1
  37. package/dist/929.js +1 -0
  38. package/dist/929.js.map +1 -0
  39. package/dist/960.js +1 -0
  40. package/dist/960.js.map +1 -0
  41. package/dist/kenyaemr-esm-morgue-app.js +1 -1
  42. package/dist/kenyaemr-esm-morgue-app.js.buildmanifest.json +371 -127
  43. package/dist/kenyaemr-esm-morgue-app.js.map +1 -1
  44. package/dist/main.js +1 -1
  45. package/dist/main.js.map +1 -1
  46. package/dist/routes.json +1 -1
  47. package/package.json +1 -1
  48. package/src/autosuggest/autosuggest.component.tsx +162 -0
  49. package/src/autosuggest/autosuggest.scss +61 -0
  50. package/src/autosuggest/patient-search-info.component.tsx +75 -0
  51. package/src/autosuggest/patient-search-info.scss +62 -0
  52. package/src/autosuggest/search-empty-state.component.tsx +21 -0
  53. package/src/autosuggest/search-empty-state.scss +18 -0
  54. package/src/card/avail-compartment.compartment.tsx +40 -41
  55. package/src/card/compartment-view.compartment.tsx +37 -14
  56. package/src/card/compartment.scss +18 -13
  57. package/src/card/compartmentSharing.component.tsx +21 -0
  58. package/src/card/compartmentSharing.scss +24 -0
  59. package/src/card/empty-compartment.component.tsx +11 -7
  60. package/src/card/empty-compartment.scss +61 -0
  61. package/src/component/deceasedInfo/deceased-info.component.tsx +1 -1
  62. package/src/component/main.component.tsx +1 -7
  63. package/src/component/next-of-kin-details/nextOfKinDetails.component.tsx +50 -0
  64. package/src/component/next-of-kin-details/nextOfKinDetails.scss +37 -0
  65. package/src/config-schema.ts +30 -22
  66. package/src/extension/actionButton.component.tsx +74 -0
  67. package/src/extension/actionButton.scss +69 -0
  68. package/src/extension/deceasedInfoBanner.component.tsx +57 -0
  69. package/src/hook/useAdmitPatient.ts +285 -0
  70. package/src/hook/useDeceasedPatients.ts +12 -0
  71. package/src/hook/useDischargedPatient.ts +55 -0
  72. package/src/hook/useMorgue.resource.ts +11 -120
  73. package/src/hook/useMortuaryAdmissionLocation.ts +64 -0
  74. package/src/hook/usePersonAttributes.ts +65 -0
  75. package/src/index.ts +4 -0
  76. package/src/routes.json +24 -7
  77. package/src/tables/admitted-queue.component.tsx +17 -21
  78. package/src/tables/discharge-queue.component.tsx +33 -27
  79. package/src/tables/generic-table.component.tsx +44 -19
  80. package/src/tabs/tabs.component.tsx +36 -16
  81. package/src/tabs/tabs.scss +3 -186
  82. package/src/types/index.ts +291 -9
  83. package/src/utils/utils.ts +55 -4
  84. package/src/workspaces/admit-body.scss +46 -0
  85. package/src/workspaces/admit-body.workspace.tsx +79 -0
  86. package/src/workspaces/discharge-body.scss +2 -2
  87. package/src/workspaces/discharge-body.workspace.tsx +157 -101
  88. package/src/workspaces/patientAdditionalInfoForm.workspace.tsx +141 -218
  89. package/src/workspaces/swap-unit.scss +46 -0
  90. package/src/workspaces/swap-unit.workspace.tsx +168 -0
  91. package/translations/en.json +22 -7
  92. package/dist/340.js +0 -1
  93. package/dist/340.js.map +0 -1
  94. package/dist/38.js +0 -1
  95. package/dist/38.js.map +0 -1
  96. package/dist/485.js +0 -2
  97. package/dist/553.js +0 -1
  98. package/dist/553.js.map +0 -1
  99. package/dist/561.js.map +0 -1
  100. package/dist/592.js +0 -2
  101. package/dist/592.js.map +0 -1
  102. package/dist/759.js +0 -1
  103. package/dist/759.js.map +0 -1
  104. package/dist/942.js.map +0 -1
  105. package/dist/987.js +0 -2
  106. package/dist/987.js.map +0 -1
  107. package/src/component/deceasedInfo/deceased-header.component.tsx +0 -37
  108. package/src/tables/waiting-queue.component.tsx +0 -91
  109. /package/dist/{987.js.LICENSE.txt → 659.js.LICENSE.txt} +0 -0
  110. /package/dist/{485.js.LICENSE.txt → 675.js.LICENSE.txt} +0 -0
  111. /package/dist/{592.js.LICENSE.txt → 909.js.LICENSE.txt} +0 -0
@@ -29,88 +29,6 @@ import useSWRImmutable from 'swr/immutable';
29
29
  import { makeUrlUrl } from '../utils/utils';
30
30
  import type { Observable } from 'rxjs';
31
31
 
32
- const getMorguePatientStatus = async (
33
- patientUuid: string,
34
- morgueVisitTypeUuid: string,
35
- morgueDischargeEncounter: string,
36
- ): Promise<'discharged' | 'admitted' | 'awaiting'> => {
37
- const customRepresentation = 'custom:(visitType:(uuid),startDatetime,stopDatetime,encounters:(encounterType:(uuid)))';
38
- const url = `${restBaseUrl}/visit?v=${customRepresentation}&patient=${patientUuid}&limit=1`;
39
- const response = await openmrsFetch<{
40
- results: Array<{
41
- visitType: { uuid: string };
42
- startDatetime: string;
43
- stopDatetime: any;
44
- encounters: Array<{
45
- encounterType: {
46
- uuid: string;
47
- };
48
- }>;
49
- }>;
50
- }>(url);
51
- const visit = response?.data?.results[0];
52
- const hasDischargeEncounter = visit?.encounters?.some(
53
- (encounter) => encounter.encounterType.uuid === morgueDischargeEncounter,
54
- );
55
- const isMorgueVisit = visit?.visitType?.uuid === morgueVisitTypeUuid;
56
- const isVisitActive = !(typeof visit?.stopDatetime === 'string');
57
-
58
- if (!isMorgueVisit) {
59
- return 'awaiting';
60
- }
61
- if (hasDischargeEncounter) {
62
- return 'discharged';
63
- }
64
- return 'admitted';
65
- };
66
- export const useDeceasedPatient = (searchTerm?: string) => {
67
- const { morgueVisitTypeUuid, morgueDischargeEncounterTypeUuid } = useConfig<ConfigObject>();
68
- const [deceasedPatient, setDeceasedPatient] = useState([]);
69
- const [isLoadingStatus, setIsLoadingStatus] = useState(false);
70
- const [statusError, setStatusError] = useState();
71
- const customRepresentation =
72
- 'custom:(uuid,display,identifiers:(identifier,uuid,preferred,location:(uuid,name)),person:(uuid,display,gender,birthdate,dead,age,deathDate,causeOfDeath:(uuid,display),preferredAddress:(uuid,stateProvince,countyDistrict,address4)))';
73
- const uril = makeUrlUrl(`${restBaseUrl}/morgue/patient?v=${customRepresentation}&dead=true&q=${searchTerm}`);
74
- const pageSize = 10;
75
- const {
76
- data: paginatedData,
77
- error: paginatedError,
78
- isLoading: isPaginatedLoading,
79
- } = useOpenmrsPagination<PaginatedResponse>(uril, pageSize);
80
-
81
- useEffect(() => {
82
- if (paginatedData?.length) {
83
- (async () => {
84
- try {
85
- setIsLoadingStatus(true);
86
- const status = await Promise.all(
87
- paginatedData.map((data) =>
88
- getMorguePatientStatus(data.person?.uuid, morgueVisitTypeUuid, morgueDischargeEncounterTypeUuid),
89
- ),
90
- );
91
-
92
- setDeceasedPatient(
93
- paginatedData.map((patient, index) => ({
94
- ...patient,
95
- status: status[index],
96
- })),
97
- );
98
- } catch (error) {
99
- setStatusError(error);
100
- } finally {
101
- setIsLoadingStatus(false);
102
- }
103
- })();
104
- }
105
- }, [morgueVisitTypeUuid, morgueDischargeEncounterTypeUuid, paginatedData]);
106
-
107
- return {
108
- data: deceasedPatient,
109
- error: paginatedError ?? statusError,
110
- isLoading: isPaginatedLoading || isLoadingStatus,
111
- };
112
- };
113
-
114
32
  export const useVisitType = () => {
115
33
  const customRepresentation = 'custom:(uuid,display,name)';
116
34
  const url = `${restBaseUrl}/visittype?v=${customRepresentation}`;
@@ -137,7 +55,6 @@ export const usePaymentModes = (excludeWaiver: boolean = true) => {
137
55
  error,
138
56
  };
139
57
  };
140
-
141
58
  export const useBillableItems = () => {
142
59
  const url = `${restBaseUrl}/cashier/billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(uuid,display),servicePrices:(uuid,name,price,paymentMode))`;
143
60
  const { data, isLoading, error } = useSWR<{ data: { results: Array<OpenmrsResource> } }>(url, openmrsFetch);
@@ -152,53 +69,19 @@ export const useBillableItems = () => {
152
69
  setSearchTerm,
153
70
  };
154
71
  };
155
-
156
- export const useMorgueCompartment = () => {
157
- const { morgueCompartmentTagUuid } = useConfig<ConfigObject>();
158
- const customRepresentation = 'custom:(uuid,display,name)';
159
- const url = `${restBaseUrl}/location?v=${customRepresentation}&tag=${morgueCompartmentTagUuid}`;
160
- const { data, isLoading, error } = useSWR<FetchResponse<{ results: Array<Location> }>>(url, openmrsFetch);
161
-
162
- return {
163
- morgueCompartments: data?.data?.results ?? [],
164
- isLoading,
165
- error,
166
- };
167
- };
168
-
72
+ // // Used
169
73
  export const createPatientBill = (payload) => {
170
74
  const postUrl = `${restBaseUrl}/cashier/bill`;
171
75
  return openmrsFetch(postUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload });
172
76
  };
173
-
77
+ // Used
174
78
  export const useCashPoint = () => {
175
79
  const url = `/ws/rest/v1/cashier/cashPoint`;
176
80
  const { data, isLoading, error } = useSWR<{ data: { results: Array<OpenmrsResource> } }>(url, openmrsFetch);
177
81
 
178
82
  return { isLoading, error, cashPoints: data?.data?.results ?? [] };
179
83
  };
180
-
181
- export const startVisitWithEncounter = (payload) => {
182
- const postUrl = `${restBaseUrl}/encounter`;
183
- return openmrsFetch(postUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload });
184
- };
185
- export const useActiveMorgueVisit = (patientUuid: string) => {
186
- const customRepresentation = 'custom:(visitType:(uuid),startDatetime,stopDatetime,encounters:(encounterType:(uuid)))';
187
- const url = `${restBaseUrl}/visit?v=${customRepresentation}&includeInactive=false&patient=${patientUuid}&limit=1`;
188
- const { data, error, isLoading } = useSWR<FetchResponse<{ results: Array<Visit> }>>(url, openmrsFetch);
189
- const activeDeceased = data?.data?.results;
190
-
191
- return { data: activeDeceased, error, isLoading };
192
- };
193
- const usePerson = (uuid: string) => {
194
- const customRepresentation = `custom:(uuid,display,gender,birthdate,dead,age,deathDate,causeOfDeath:(uuid,display))`;
195
- const url = `${restBaseUrl}/person/${uuid}?v=${customRepresentation}`;
196
- const { isLoading, error, data } = useSWR<FetchResponse<Patient['person']>>(url, openmrsFetch);
197
- const person = data?.data;
198
- return { isLoading, error, person };
199
- };
200
-
201
- export default usePerson;
84
+ // // Used
202
85
 
203
86
  export function useVisitQueueEntry(patientUuid, visitUuid): UseVisitQueueEntries {
204
87
  const apiUrl = `${restBaseUrl}/visit-queue-entry?v=full&patient=${patientUuid}`;
@@ -237,7 +120,15 @@ export function useVisitQueueEntry(patientUuid, visitUuid): UseVisitQueueEntries
237
120
  mutate,
238
121
  };
239
122
  }
123
+ export const usePerson = (uuid: string) => {
124
+ const customRepresentation = `custom:(uuid,display,gender,birthdate,dead,age,deathDate,causeOfDeath:(uuid,display))`;
125
+ const url = `${restBaseUrl}/person/${uuid}?v=${customRepresentation}`;
126
+ const { isLoading, error, data } = useSWR<FetchResponse<Patient['person']>>(url, openmrsFetch);
127
+ const person = data?.data;
128
+ return { isLoading, error, person };
129
+ };
240
130
 
131
+ // Used
241
132
  export function removeQueuedPatient(
242
133
  queueUuid: string,
243
134
  queueEntryUuid: string,
@@ -0,0 +1,64 @@
1
+ import {
2
+ type FetchResponse,
3
+ openmrsFetch,
4
+ restBaseUrl,
5
+ useFeatureFlag,
6
+ type Location,
7
+ useSession,
8
+ } from '@openmrs/esm-framework';
9
+ import useSWR from 'swr';
10
+ import useSWRImmutable from 'swr/immutable';
11
+ import { useParams } from 'react-router-dom';
12
+ import { type MortuaryLocationFetchResponse, type FHIREncounter } from '../types';
13
+
14
+ export function useLocation(locationUuid: string, rep: string = 'custom:(display,uuid)') {
15
+ return useSWRImmutable<FetchResponse<Location>>(
16
+ locationUuid ? `${restBaseUrl}/location/${locationUuid}?v=${rep}` : null,
17
+ openmrsFetch,
18
+ );
19
+ }
20
+
21
+ export function useMortuaryLocation(): {
22
+ location: Location | undefined;
23
+ isLoadingLocation: boolean;
24
+ errorFetchingLocation: Error | undefined;
25
+ invalidLocation: boolean;
26
+ } {
27
+ const { locationUuid: locationUuidFromUrl } = useParams();
28
+ const { sessionLocation } = useSession();
29
+
30
+ const {
31
+ data: locationResponse,
32
+ isLoading: isLoadingLocation,
33
+ error: errorFetchingLocation,
34
+ } = useLocation(locationUuidFromUrl ?? null);
35
+
36
+ const invalidLocation = !!locationUuidFromUrl && !!errorFetchingLocation;
37
+
38
+ return {
39
+ location: locationUuidFromUrl ? locationResponse?.data : sessionLocation,
40
+ isLoadingLocation,
41
+ errorFetchingLocation,
42
+ invalidLocation,
43
+ };
44
+ }
45
+
46
+ const requestRep =
47
+ 'custom:(ward,totalBeds,occupiedBeds,bedLayouts:(rowNumber,columnNumber,bedNumber,bedId,bedUuid,status,location,patients:(person:full,identifiers,uuid)))';
48
+
49
+ export function useAdmissionLocation(rep: string = requestRep) {
50
+ const { location } = useMortuaryLocation();
51
+ const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
52
+
53
+ const apiUrl = location?.uuid ? `${restBaseUrl}/admissionLocation/${location.uuid}?v=${rep}` : null;
54
+
55
+ const { data, ...rest } = useSWR<FetchResponse<MortuaryLocationFetchResponse>, Error>(
56
+ isBedManagementModuleInstalled ? apiUrl : null,
57
+ openmrsFetch,
58
+ );
59
+
60
+ return {
61
+ admissionLocation: data?.data,
62
+ ...rest,
63
+ };
64
+ }
@@ -0,0 +1,65 @@
1
+ import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { Patient, PatientInfo } from '../types';
3
+ import useSWR from 'swr';
4
+
5
+ export const usePersonAttributes = (personUuid: string) => {
6
+ const { data, error, isLoading } = useSWR<FetchResponse<{ results: Patient['person']['attributes'] }>>(
7
+ personUuid ? `${restBaseUrl}/person/${personUuid}/attribute` : null,
8
+ openmrsFetch,
9
+ );
10
+
11
+ const updatePersonAttributes = async (
12
+ payload: { attributeType: string; value: string },
13
+ personUuid: string,
14
+ attributeUuid: string,
15
+ ) => {
16
+ const url = `${restBaseUrl}/person/${personUuid}/attribute/${attributeUuid}`;
17
+ return openmrsFetch(url, {
18
+ method: 'POST',
19
+ body: JSON.stringify(payload),
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ },
23
+ });
24
+ };
25
+
26
+ const createPersonAttribute = async (payload: { attributeType: string; value: string }, personUuid: string) => {
27
+ const url = `${restBaseUrl}/person/${personUuid}/attribute`;
28
+ return openmrsFetch(url, {
29
+ method: 'POST',
30
+ body: JSON.stringify(payload),
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ },
34
+ });
35
+ };
36
+
37
+ const createOrUpdatePersonAttribute = async (
38
+ personUuid: string,
39
+ payload: { attributeType: string; value: string },
40
+ person: PatientInfo,
41
+ ) => {
42
+ const { attributeType, value } = payload;
43
+
44
+ const existingAttribute = person.attributes.find((attr) => attr.uuid === attributeType);
45
+
46
+ if (existingAttribute) {
47
+ return updatePersonAttributes(
48
+ { attributeType: existingAttribute.uuid, value },
49
+ personUuid,
50
+ existingAttribute.uuid,
51
+ );
52
+ } else {
53
+ return createPersonAttribute({ attributeType, value }, personUuid);
54
+ }
55
+ };
56
+
57
+ return {
58
+ personAttributes: data?.data?.results ?? [],
59
+ error,
60
+ isLoading,
61
+ updatePersonAttributes,
62
+ createPersonAttribute,
63
+ createOrUpdatePersonAttribute,
64
+ };
65
+ };
package/src/index.ts CHANGED
@@ -38,3 +38,7 @@ export const patientAdditionalInfoForm = getAsyncLifecycle(
38
38
  options,
39
39
  );
40
40
  export const dischargeBodyForm = getAsyncLifecycle(() => import('./workspaces/discharge-body.workspace'), options);
41
+ export const admitBodyForm = getAsyncLifecycle(() => import('./workspaces/admit-body.workspace'), options);
42
+ export const swapForm = getAsyncLifecycle(() => import('./workspaces/swap-unit.workspace'), options);
43
+ export const actionBarButtons = getAsyncLifecycle(() => import('./extension/actionButton.component'), options);
44
+ export const bannerInfo = getAsyncLifecycle(() => import('./extension/deceasedInfoBanner.component'), options);
package/src/routes.json CHANGED
@@ -16,14 +16,19 @@
16
16
  }
17
17
  },
18
18
  {
19
- "component": "peerCalendarDashboardLink",
20
- "name": "peer-calendar-dashboard-link",
21
- "slot": "homepage-dashboard-slot",
19
+ "name": "action-buttons",
20
+ "component": "actionBarButtons",
21
+ "slot": "mortuary-action-buttons-slot",
22
+ "meta": {
23
+ "fullWidth": false
24
+ }
25
+ },
26
+ {
27
+ "name": "deceased-banner-info",
28
+ "component": "bannerInfo",
29
+ "slot": "deceased-banner-info-slot",
22
30
  "meta": {
23
- "name": "peer-calendar",
24
- "title": "Peer Calendar",
25
- "slot": "peer-calendar-dashboard-slot",
26
- "path": "peer-management"
31
+ "fullWidth": false
27
32
  }
28
33
  },
29
34
  {
@@ -44,6 +49,18 @@
44
49
  "component": "dischargeBodyForm",
45
50
  "title": "discharge body form",
46
51
  "type": "other-form"
52
+ },
53
+ {
54
+ "name": "admit-body-form",
55
+ "component": "admitBodyForm",
56
+ "title": "Admission form",
57
+ "type": "other-form"
58
+ },
59
+ {
60
+ "name": "swap-unit-form",
61
+ "component": "swapForm",
62
+ "title": "Swap form",
63
+ "type": "other-form"
47
64
  }
48
65
  ],
49
66
  "pages": [
@@ -3,50 +3,46 @@ import { useTranslation } from 'react-i18next';
3
3
  import DeceasedFilter from '../header/admitted-queue-header.component';
4
4
  import styles from './admitted-queue.scss';
5
5
  import CompartmentView from '../card/compartment-view.compartment';
6
- import { useDeceasedPatient } from '../hook/useMorgue.resource';
6
+ import { useAdmissionLocation } from '../hook/useMortuaryAdmissionLocation';
7
7
  import { InlineLoading } from '@carbon/react';
8
8
  import { CardHeader, ErrorState } from '@openmrs/esm-patient-common-lib';
9
9
  import EmptyMorgueAdmission from '../empty-state/empty-morgue-admission.component';
10
+ import useEmrConfiguration from '../hook/useAdmitPatient';
11
+ import { useDischargedPatient } from '../hook/useDischargedPatient';
10
12
 
11
13
  export const AdmittedQueue: React.FC = () => {
12
- const { data: deceasedPatients, error, isLoading } = useDeceasedPatient();
13
14
  const { t } = useTranslation();
14
15
  const [searchQuery, setSearchQuery] = useState('');
15
-
16
+ const { admissionLocation, isLoading, error } = useAdmissionLocation();
16
17
  const handleSearchChange = (query: string) => {
17
18
  setSearchQuery(query);
18
19
  };
19
20
 
20
- const admittedPatients = deceasedPatients?.filter((patient) => patient.status === 'admitted') || [];
21
21
  if (isLoading) {
22
- return (
23
- <InlineLoading
24
- status="active"
25
- iconDescription="Loading"
26
- description={t('pullingCompartment', 'Pulling compartments data.....')}
27
- />
28
- );
22
+ return <InlineLoading description={t('loading', 'Loading...')} />;
29
23
  }
30
24
 
31
25
  if (error) {
32
- return <ErrorState error={error} headerTitle={t('allocation', 'Allocation')} />;
26
+ return <ErrorState headerTitle={t('errorLoadingData', 'Error loading data')} error={error} />;
33
27
  }
34
28
 
35
- if (admittedPatients?.length === 0) {
36
- return (
37
- <EmptyMorgueAdmission
38
- title={t('allocations', 'Allocation')}
39
- subTitle={t('noAdmittedBodies', 'There are no admitted bodies')}
40
- />
41
- );
42
- }
29
+ const filteredBedLayouts = admissionLocation?.bedLayouts?.filter((bed) => {
30
+ return bed.patients.some((patient) => patient?.person?.display?.toLowerCase().includes(searchQuery.toLowerCase()));
31
+ });
43
32
 
44
33
  return (
45
34
  <div className={styles.layoutWrapper}>
46
35
  <CardHeader title={t('allocation', 'Allocation')} children={''} />
47
36
  <DeceasedFilter onSearchChange={handleSearchChange} />
48
37
  <div className={styles.patientCardContainer}>
49
- <CompartmentView patientVisit={{ results: admittedPatients }} searchQuery={searchQuery} />
38
+ {filteredBedLayouts?.length === 0 ? (
39
+ <EmptyMorgueAdmission
40
+ title={t('allocations', 'Allocation')}
41
+ subTitle={t('noAdmittedBodies', 'There are no admitted bodies matching your search')}
42
+ />
43
+ ) : (
44
+ <CompartmentView />
45
+ )}
50
46
  </div>
51
47
  </div>
52
48
  );
@@ -1,23 +1,26 @@
1
+ import { DataTableSkeleton } from '@carbon/react';
2
+ import { ConfigurableLink, ErrorState, formatDate, useConfig } from '@openmrs/esm-framework';
1
3
  import React from 'react';
2
- import GenericTable from './generic-table.component';
3
- import { ConfigurableLink, ErrorState, formatDate, launchWorkspace } from '@openmrs/esm-framework';
4
- import { toUpperCase } from '../helpers/expression-helper';
5
- import { Tag, Button, DataTableSkeleton, OverflowMenu, OverflowMenuItem } from '@carbon/react';
6
- import styles from './generic-table.scss';
7
4
  import { useTranslation } from 'react-i18next';
8
- import { useDeceasedPatient } from '../hook/useMorgue.resource';
9
- import { formatDateTime } from '../utils/utils';
5
+ import styles from './generic-table.scss';
6
+ import GenericTable from './generic-table.component';
7
+ import { ConfigObject } from '../config-schema';
8
+ import usePatients from '../hook/useDischargedPatient';
10
9
 
11
10
  interface DischargedProps {
11
+ dischargedPatientUuids: string[];
12
12
  isLoading: boolean;
13
- deceasedPatients: any;
14
13
  error: any;
15
14
  }
16
15
 
17
- export const DischargedBodies: React.FC<DischargedProps> = ({ isLoading, deceasedPatients, error }) => {
16
+ export const DischargedBodies: React.FC<DischargedProps> = ({ isLoading, error, dischargedPatientUuids }) => {
18
17
  const { t } = useTranslation();
19
18
  const dischargedInLine = t('dischargeBodies', 'Discharged bodies');
20
19
  const patientChartUrl = '${openmrsSpaBase}/patient/${patientUuid}/chart/deceased-panel';
20
+ const { patients, isLoading: isLoadingPatients, error: patientsError } = usePatients(dischargedPatientUuids);
21
+
22
+ const { nextOfKinAddressUuid, nextOfKinNameUuid, nextOfKinPhoneUuid, nextOfKinRelationshipUuid } =
23
+ useConfig<ConfigObject>();
21
24
 
22
25
  const genericTableHeader = [
23
26
  { header: 'Patient Name', key: 'name' },
@@ -26,7 +29,6 @@ export const DischargedBodies: React.FC<DischargedProps> = ({ isLoading, decease
26
29
  { header: 'Age', key: 'age' },
27
30
  { header: 'Date of Death', key: 'deathDate' },
28
31
  { header: 'Cause of Death', key: 'causeOfDeath' },
29
- { header: 'Status', key: 'status' },
30
32
  ];
31
33
 
32
34
  if (isLoading) {
@@ -49,33 +51,37 @@ export const DischargedBodies: React.FC<DischargedProps> = ({ isLoading, decease
49
51
  return <ErrorState error={error} headerTitle={t('dischargeBodies', 'Discharged bodies')} />;
50
52
  }
51
53
 
52
- const dischargedDeceased = deceasedPatients?.filter((patient) => patient?.status === 'discharged') || [];
54
+ const getAttributeValue = (patient, uuid) => {
55
+ return patient?.person?.attributes?.find((attr) => attr?.attributeType?.uuid === uuid)?.value || '--';
56
+ };
57
+
58
+ const rows = patients?.map((patient) => {
59
+ const openmrsID = patient?.identifiers?.find((id) => id.display.startsWith('OpenMRS ID'))?.display.split(' = ')[1];
53
60
 
54
- const rows = dischargedDeceased.map((patient, index) => {
55
- const openMrsId =
56
- patient?.patient?.identifiers
57
- ?.find((id) => id.display.startsWith('OpenMRS ID'))
58
- ?.display.split('=')[1]
59
- ?.trim() || '--';
61
+ const nextOfKin = {
62
+ name: getAttributeValue(patient, nextOfKinNameUuid),
63
+ phone: getAttributeValue(patient, nextOfKinPhoneUuid),
64
+ address: getAttributeValue(patient, nextOfKinAddressUuid),
65
+ relationship: getAttributeValue(patient, nextOfKinRelationshipUuid),
66
+ };
60
67
 
61
68
  return {
62
- id: `${patient?.patient?.uuid}`,
69
+ id: patient?.uuid,
63
70
  name: (
64
71
  <ConfigurableLink
65
72
  style={{ textDecoration: 'none', maxWidth: '50%' }}
66
73
  to={patientChartUrl}
67
- templateParams={{ patientUuid: patient?.person?.uuid }}>
68
- {patient?.person?.person?.display?.toUpperCase()}
74
+ templateParams={{ patientUuid: patient?.uuid }}>
75
+ {patient?.person?.display?.toUpperCase()}
69
76
  </ConfigurableLink>
70
77
  ),
71
- gender: patient?.person?.person?.gender || '--',
72
- age: patient?.person?.person?.age || '--',
73
- identifier: openMrsId,
74
- deathDate: formatDateTime(patient?.person?.person?.deathDate) || '--',
75
- causeOfDeath: patient?.person?.person?.causeOfDeath?.display || '--',
76
- status: <Tag type="magenta">{patient?.status || '--'}</Tag>,
78
+ gender: patient?.person?.gender || '--',
79
+ age: patient?.person?.age || '--',
80
+ identifier: openmrsID || '--',
81
+ deathDate: formatDate(new Date(patient?.person?.deathDate)) || '--',
82
+ causeOfDeath: patient?.person?.causeOfDeath?.display || '--',
83
+ nextOfKin: nextOfKin, // Ensure nextOfKin is included in the row
77
84
  };
78
85
  });
79
-
80
86
  return <GenericTable rows={rows} headers={genericTableHeader} title={dischargedInLine} />;
81
87
  };
@@ -10,12 +10,17 @@ import {
10
10
  TableHead,
11
11
  TableHeader,
12
12
  TableRow,
13
+ TableExpandRow,
14
+ TableExpandedRow,
13
15
  Search,
16
+ TableExpandHeader,
14
17
  } from '@carbon/react';
15
18
  import { CardHeader, PatientChartPagination } from '@openmrs/esm-patient-common-lib';
16
19
  import { useLayoutType, usePagination } from '@openmrs/esm-framework';
17
20
  import styles from './generic-table.scss';
18
21
  import EmptyDeceasedSearch from '../empty-state/empty-morgue-admission.component';
22
+ import { Patient } from '../types';
23
+ import NextOfKinDetails from '../component/next-of-kin-details/nextOfKinDetails.component';
19
24
 
20
25
  interface GenericTableProps {
21
26
  rows: any[];
@@ -31,7 +36,7 @@ const GenericTable: React.FC<GenericTableProps> = ({ rows, headers, actionColumn
31
36
 
32
37
  const [searchTerm, setSearchTerm] = useState('');
33
38
 
34
- const filteredRows = rows.filter((row) =>
39
+ const filteredRows = rows?.filter((row) =>
35
40
  headers.some((header) => row[header.key]?.toString().toLowerCase().includes(searchTerm.toLowerCase())),
36
41
  );
37
42
 
@@ -53,12 +58,12 @@ const GenericTable: React.FC<GenericTableProps> = ({ rows, headers, actionColumn
53
58
  />
54
59
  </div>
55
60
 
56
- {rows.length === 0 ? (
61
+ {rows?.length === 0 ? (
57
62
  <EmptyDeceasedSearch
58
63
  title={t('noWaitingList', 'Waiting List')}
59
64
  subTitle={t('noDeceasedPersons', 'There are no deceased persons on the waiting list')}
60
65
  />
61
- ) : searchTerm && filteredRows.length === 0 ? (
66
+ ) : searchTerm && filteredRows?.length === 0 ? (
62
67
  <EmptyDeceasedSearch
63
68
  title={t('noResultFound', 'Sorry, no results found')}
64
69
  subTitle={t('searchForADeceased', "Try to search again using the deceased patient's unique ID number")}
@@ -66,18 +71,25 @@ const GenericTable: React.FC<GenericTableProps> = ({ rows, headers, actionColumn
66
71
  ) : (
67
72
  <div className={styles.genericTable}>
68
73
  <DataTable rows={paginatedData} headers={headers} isSortable size={isTablet ? 'lg' : 'sm'} useZebraStyles>
69
- {({ rows, headers, getHeaderProps, getTableProps }) => (
70
- <TableContainer className={styles.tableContainer}>
74
+ {({
75
+ rows: tableRows,
76
+ headers,
77
+ getHeaderProps,
78
+ getTableProps,
79
+ getRowProps,
80
+ getTableContainerProps,
81
+ getExpandHeaderProps,
82
+ }) => (
83
+ <TableContainer {...getTableContainerProps()} className={styles.tableContainer}>
71
84
  <Table {...getTableProps()}>
72
85
  <TableHead>
73
86
  <TableRow>
74
- {headers.map((header) => (
87
+ <TableExpandHeader enableToggle {...getExpandHeaderProps()} />
88
+ {headers?.map((header, i) => (
75
89
  <TableHeader
76
- key={header.key}
77
- className={classNames(styles.productiveHeading01, styles.text02)}
90
+ key={i}
78
91
  {...getHeaderProps({
79
92
  header,
80
- isSortable: header.isSortable,
81
93
  })}>
82
94
  {header.header}
83
95
  </TableHeader>
@@ -86,22 +98,35 @@ const GenericTable: React.FC<GenericTableProps> = ({ rows, headers, actionColumn
86
98
  </TableRow>
87
99
  </TableHead>
88
100
  <TableBody>
89
- {rows.map((row) => (
90
- <TableRow key={row.id}>
91
- {row.cells.map((cell) => (
92
- <TableCell key={cell.id}>{cell.value}</TableCell>
93
- ))}
94
- {actionColumn && <TableCell>{actionColumn(row)}</TableCell>}
95
- </TableRow>
96
- ))}
101
+ {tableRows?.map((row, i) => {
102
+ const originalRow = rows.find((r) => r.id === row.id);
103
+
104
+ return (
105
+ <React.Fragment key={row.id}>
106
+ <TableExpandRow {...getRowProps({ row })}>
107
+ {row.cells.map((cell) => (
108
+ <TableCell key={cell.id}>{cell.value}</TableCell>
109
+ ))}
110
+ {actionColumn && <TableCell>{actionColumn(row)}</TableCell>}
111
+ </TableExpandRow>
112
+ {row.isExpanded && (
113
+ <TableExpandedRow colSpan={headers.length + 1}>
114
+ <div className={styles.expandedRowContent}>
115
+ {originalRow && <NextOfKinDetails nextOfKin={originalRow.nextOfKin} />}
116
+ </div>
117
+ </TableExpandedRow>
118
+ )}
119
+ </React.Fragment>
120
+ );
121
+ })}
97
122
  </TableBody>
98
123
  </Table>
99
124
  </TableContainer>
100
125
  )}
101
126
  </DataTable>
102
127
  <PatientChartPagination
103
- currentItems={paginatedData.length}
104
- totalItems={filteredRows.length}
128
+ currentItems={paginatedData?.length}
129
+ totalItems={filteredRows?.length}
105
130
  onPageNumberChange={({ page }) => goTo(page)}
106
131
  pageNumber={currentPage}
107
132
  pageSize={pageSize}