@kenyaemr/esm-morgue-app 5.4.2-pre.2283 → 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 +74 -21
  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,261 @@
1
+ import {
2
+ FetchResponse,
3
+ openmrsFetch,
4
+ restBaseUrl,
5
+ fhirBaseUrl,
6
+ useConfig,
7
+ useFhirPagination,
8
+ } from '@openmrs/esm-framework';
9
+ import useSWR from 'swr';
10
+ import {
11
+ MappedVisitQueueEntry,
12
+ MortuaryPatient,
13
+ UseVisitQueueEntries,
14
+ VisitQueueEntry,
15
+ MortuaryLocationResponse,
16
+ Entry,
17
+ } from '../types';
18
+ import React, { useMemo, useEffect, useState } from 'react';
19
+ import { ConfigObject } from '../config-schema';
20
+
21
+ interface MortuaryApiResponse {
22
+ results: MortuaryPatient[];
23
+ }
24
+
25
+ export const useAwaitingQueuePatients = (admissionLocation?: MortuaryLocationResponse) => {
26
+ const { morgueDischargeEncounterTypeUuid } = useConfig<ConfigObject>();
27
+ const [currPageSize, setCurrPageSize] = useState(10);
28
+
29
+ const customRepresentation =
30
+ '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)))';
31
+ const url = `${restBaseUrl}/morgue/patient?v=${customRepresentation}&dead=true`;
32
+ const { isLoading, error, data, mutate } = useSWR<FetchResponse<MortuaryApiResponse>>(url, openmrsFetch);
33
+
34
+ const locationUuid = admissionLocation?.ward?.uuid;
35
+ const dischargeUrl =
36
+ locationUuid && morgueDischargeEncounterTypeUuid
37
+ ? `${fhirBaseUrl}/Encounter?_summary=data&type=${morgueDischargeEncounterTypeUuid}&location=${locationUuid}`
38
+ : null;
39
+
40
+ const {
41
+ data: dischargeData,
42
+ isLoading: dischargeLoading,
43
+ error: dischargeError,
44
+ mutate: mutateDischarge,
45
+ } = useFhirPagination<Entry>(dischargeUrl, currPageSize);
46
+
47
+ const dischargedPatientUuids = useMemo(() => {
48
+ if (!dischargeData || !Array.isArray(dischargeData)) {
49
+ return [];
50
+ }
51
+
52
+ const uuids = dischargeData
53
+ .map((entry: Entry) => {
54
+ const reference = entry?.subject?.reference;
55
+ if (reference && reference.startsWith('Patient/')) {
56
+ return reference.split('/')[1];
57
+ }
58
+ return undefined;
59
+ })
60
+ .filter((uuid: string | undefined) => uuid);
61
+
62
+ return [...new Set(uuids)];
63
+ }, [dischargeData]);
64
+
65
+ const admittedPatientUuids = useMemo(() => {
66
+ if (!admissionLocation?.bedLayouts) {
67
+ return [];
68
+ }
69
+
70
+ return admissionLocation.bedLayouts
71
+ .flatMap((bed) => bed.patients?.map((patient) => patient.uuid))
72
+ .filter(Boolean) as string[];
73
+ }, [admissionLocation]);
74
+
75
+ const filteredAwaitingPatients = useMemo(() => {
76
+ if (!data?.data?.results) {
77
+ return [];
78
+ }
79
+
80
+ return data.data.results.filter((patient) => {
81
+ const patientUuid = patient?.person?.person?.uuid;
82
+ if (!patientUuid) {
83
+ return false;
84
+ }
85
+
86
+ if (dischargedPatientUuids.includes(patientUuid)) {
87
+ return false;
88
+ }
89
+
90
+ if (admittedPatientUuids.includes(patientUuid)) {
91
+ return false;
92
+ }
93
+
94
+ return true;
95
+ });
96
+ }, [data, dischargedPatientUuids, admittedPatientUuids]);
97
+
98
+ const admittedPatients = useMemo(() => {
99
+ if (!admissionLocation?.bedLayouts) {
100
+ return [];
101
+ }
102
+
103
+ return admissionLocation.bedLayouts.flatMap((bed) => bed.patients || []).filter(Boolean);
104
+ }, [admissionLocation]);
105
+
106
+ const { dischargedPatients, dischargedPatientsCount } = useMemo(() => {
107
+ if (!dischargeData || !Array.isArray(dischargeData) || dischargeLoading) {
108
+ return { dischargedPatients: [], dischargedPatientsCount: 0 };
109
+ }
110
+
111
+ const discharged = dischargeData.filter((entry) => entry?.subject?.reference);
112
+ return {
113
+ dischargedPatients: discharged,
114
+ dischargedPatientsCount: discharged.length,
115
+ };
116
+ }, [dischargeData, dischargeLoading]);
117
+
118
+ const mutateAll = React.useCallback(() => {
119
+ mutate();
120
+ if (mutateDischarge) {
121
+ mutateDischarge();
122
+ }
123
+ }, [mutate, mutateDischarge]);
124
+
125
+ return {
126
+ awaitingQueueDeceasedPatients: filteredAwaitingPatients,
127
+ admittedPatients,
128
+ dischargedPatients,
129
+ dischargedPatientsCount,
130
+ isLoadingAwaitingQueuePatients: isLoading,
131
+ isLoadingDischarge: dischargeLoading,
132
+ isLoadingAll: isLoading || dischargeLoading,
133
+ errorFetchingAwaitingQueuePatients: error || dischargeError,
134
+ mutateAwaitingQueuePatients: mutateAll,
135
+ mutateAll,
136
+ };
137
+ };
138
+
139
+ /**
140
+ * Custom hook specifically for awaiting patients (patients without active visits or beds)
141
+ * This hook is now simplified since the filtering is done upstream
142
+ * @param patients - Array of mortuary patients (already filtered by useAwaitingQueuePatients)
143
+ * @returns The same array of patients (already filtered upstream)
144
+ */
145
+ export const useAwaitingPatients = (patients: MortuaryPatient[]) => {
146
+ // Since useAwaitingQueuePatients already filters out discharged and admitted patients,
147
+ // we can return the patients as-is
148
+ return patients || [];
149
+ };
150
+
151
+ /**
152
+ * Fetches visit data for multiple patients
153
+ * @param patientUuids - Array of patient UUIDs
154
+ * @returns Promise that resolves to a record of visit statuses
155
+ */
156
+ const fetchPatientVisits = async (patientUuids: string[]) => {
157
+ const visitPromises = patientUuids.map(async (uuid) => {
158
+ try {
159
+ const response = await openmrsFetch(`${restBaseUrl}/visit?patient=${uuid}&includeInactive=false`);
160
+ return {
161
+ uuid,
162
+ hasActiveVisit: response.data?.results?.length > 0,
163
+ };
164
+ } catch (error) {
165
+ console.error(`Error fetching visit for patient ${uuid}:`, error);
166
+ return { uuid, hasActiveVisit: false };
167
+ }
168
+ });
169
+
170
+ const visits = await Promise.all(visitPromises);
171
+ return visits.reduce((acc, { uuid, hasActiveVisit }) => {
172
+ acc[uuid] = hasActiveVisit;
173
+ return acc;
174
+ }, {} as Record<string, boolean>);
175
+ };
176
+
177
+ /**
178
+ * Custom hook to filter mortuary patients based on their visit status
179
+ * @param patients - Array of mortuary patients
180
+ * @returns Object containing filtered patients for different statuses
181
+ */
182
+ export const useFilteredPatients = (patients: MortuaryPatient[]) => {
183
+ const [visitData, setVisitData] = useState<Record<string, boolean>>({});
184
+ const patientUuids = useMemo(() => {
185
+ return patients.map((patient) => patient?.person?.person?.uuid).filter(Boolean) as string[];
186
+ }, [patients]);
187
+
188
+ useEffect(() => {
189
+ if (patientUuids.length > 0) {
190
+ fetchPatientVisits(patientUuids).then(setVisitData);
191
+ }
192
+ }, [patientUuids]);
193
+
194
+ return useMemo(() => {
195
+ if (!patients || patients.length === 0) {
196
+ return {
197
+ awaitingAdmission: [],
198
+ admitted: [],
199
+ all: [],
200
+ };
201
+ }
202
+
203
+ const awaitingAdmission: MortuaryPatient[] = [];
204
+ const admitted: MortuaryPatient[] = [];
205
+
206
+ patients.forEach((patient) => {
207
+ const patientUuid = patient?.person?.person?.uuid;
208
+ if (!patientUuid) {
209
+ return;
210
+ }
211
+
212
+ if (visitData[patientUuid]) {
213
+ admitted.push(patient);
214
+ } else {
215
+ awaitingAdmission.push(patient);
216
+ }
217
+ });
218
+
219
+ return {
220
+ awaitingAdmission,
221
+ };
222
+ }, [patients, visitData]);
223
+ };
224
+
225
+ export function useVisitQueueEntry(patientUuid: string, visitUuid: string): UseVisitQueueEntries {
226
+ const apiUrl = `${restBaseUrl}/visit-queue-entry?v=full&patient=${patientUuid}`;
227
+ const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array<VisitQueueEntry> } }, Error>(
228
+ apiUrl,
229
+ openmrsFetch,
230
+ );
231
+ const mapVisitQueueEntryProperties = (visitQueueEntry: VisitQueueEntry): MappedVisitQueueEntry => ({
232
+ id: visitQueueEntry.uuid,
233
+ name: visitQueueEntry.queueEntry.queue.display,
234
+ patientUuid: visitQueueEntry.queueEntry.patient.uuid,
235
+ priority:
236
+ visitQueueEntry.queueEntry.priority.display === 'Urgent'
237
+ ? 'Priority'
238
+ : visitQueueEntry.queueEntry.priority.display,
239
+ priorityUuid: visitQueueEntry.queueEntry.priority.uuid,
240
+ service: visitQueueEntry.queueEntry.queue?.display,
241
+ status: visitQueueEntry.queueEntry.status.display,
242
+ statusUuid: visitQueueEntry.queueEntry.status.uuid,
243
+ visitUuid: visitQueueEntry.visit?.uuid,
244
+ visitType: visitQueueEntry.visit?.visitType?.display,
245
+ queue: visitQueueEntry.queueEntry.queue,
246
+ queueEntryUuid: visitQueueEntry.queueEntry.uuid,
247
+ });
248
+
249
+ const mappedVisitQueueEntry =
250
+ data?.data?.results
251
+ ?.map(mapVisitQueueEntryProperties)
252
+ .filter((visitQueueEntry) => visitUuid !== undefined && visitUuid === visitQueueEntry.visitUuid)
253
+ .shift() ?? null;
254
+ return {
255
+ queueEntry: mappedVisitQueueEntry,
256
+ isLoading,
257
+ error: error,
258
+ isValidating,
259
+ mutate,
260
+ };
261
+ }
@@ -0,0 +1,5 @@
1
+ @use '@carbon/colors';
2
+
3
+ .section {
4
+ border-right: 1px solid colors.$gray-20;
5
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,8 @@ import {
7
7
  } from '@openmrs/esm-framework';
8
8
  import { configSchema } from './config-schema';
9
9
  import { createLeftPanelLink } from './left-panel/morgue-left-panel-link.component';
10
+ import FormEntryWorkspace from './forms/form-entry-workspace/form-entry-workspace.workspace';
11
+ import DisposeForm from './forms/dispose-deceased-person-workspace/dispose-deceased-person.workspace';
10
12
  const moduleName = '@kenyaemr/esm-morgue-app';
11
13
 
12
14
  const options = {
@@ -27,11 +29,6 @@ export function startupApp() {
27
29
  parent: `${window.spaBase}/home`,
28
30
  },
29
31
  ]);
30
- registerFeatureFlag(
31
- 'mortuaryFeatureFlag',
32
- 'Mortuary App Service',
33
- 'Mortuary feature flag, this enables and disables the mortuary app feature',
34
- );
35
32
  }
36
33
 
37
34
  export const root = getAsyncLifecycle(() => import('./root.component'), options);
@@ -44,12 +41,21 @@ export const morgueDashboardLink = getSyncLifecycle(
44
41
  options,
45
42
  );
46
43
 
47
- export const patientAdditionalInfoForm = getAsyncLifecycle(
48
- () => import('./workspaces/patientAdditionalInfoForm.workspace'),
49
- options,
50
- );
51
- export const dischargeBodyForm = getAsyncLifecycle(() => import('./workspaces/discharge-body.workspace'), options);
52
- export const admitBodyForm = getAsyncLifecycle(() => import('./workspaces/admit-body.workspace'), options);
53
- export const swapForm = getAsyncLifecycle(() => import('./workspaces/swap-unit.workspace'), options);
54
44
  export const actionBarButtons = getAsyncLifecycle(() => import('./extension/actionButton.component'), options);
55
45
  export const bannerInfo = getAsyncLifecycle(() => import('./extension/deceasedInfoBanner.component'), options);
46
+ export const admitDeceasedPersonForm = getAsyncLifecycle(
47
+ () => import('./forms/admit-deceased-person-workspace/admit-deceased-person.workspace'),
48
+ options,
49
+ );
50
+ export const swapForm = getAsyncLifecycle(
51
+ () => import('./forms/swap-compartment-workspace/swap-unit.workspace'),
52
+ options,
53
+ );
54
+ export const dischargeBodyForm = getAsyncLifecycle(
55
+ () => import('./forms/discharge-deceased-person-workspace/discharge-body.workspace'),
56
+ options,
57
+ );
58
+ export const mortuaryFormEntry = getSyncLifecycle(FormEntryWorkspace, options);
59
+ export const disposeDeceasedPersonForm = getSyncLifecycle(DisposeForm, options);
60
+ export const mortuaryChartView = getAsyncLifecycle(() => import('./view-details/main/main.component'), options);
61
+ export const mortuarySummary = getAsyncLifecycle(() => import('./summary/summary.component'), options);
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { Layer, Tile } from '@carbon/react';
3
+ import styles from './metrics-card.scss';
4
+
5
+ interface MetricsCardProps {
6
+ label: string;
7
+ value: number | string | React.ReactNode;
8
+ headerLabel: string;
9
+ children?: React.ReactNode;
10
+ }
11
+
12
+ const MetricsCard: React.FC<MetricsCardProps> = ({ children, headerLabel, label, value }) => {
13
+ return (
14
+ <Layer className={`${children && styles.cardWithChildren} ${styles.container}`}>
15
+ <Tile className={styles.tileContainer}>
16
+ <div className={styles.tileHeader}>
17
+ <div className={styles.headerLabelContainer}>
18
+ <label className={styles.headerLabel}>{headerLabel}</label>
19
+ </div>
20
+ {children}
21
+ </div>
22
+ <div>
23
+ <label className={styles.totalsLabel}>{label}</label>
24
+ <p className={styles.totalsValue}>{value}</p>
25
+ </div>
26
+ </Tile>
27
+ </Layer>
28
+ );
29
+ };
30
+
31
+ export default MetricsCard;
@@ -0,0 +1,51 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .container {
6
+ flex-grow: 1;
7
+ }
8
+
9
+ .tileContainer {
10
+ border: 1px solid colors.$gray-20;
11
+ height: 7.875rem;
12
+ padding: layout.$spacing-05;
13
+ margin: layout.$spacing-03 layout.$spacing-03;
14
+ }
15
+
16
+ .tileHeader {
17
+ display: flex;
18
+ justify-content: space-between;
19
+ align-items: baseline;
20
+ margin-bottom: layout.$spacing-03;
21
+ }
22
+
23
+ .headerLabel {
24
+ @include type.type-style('heading-compact-01');
25
+ color: colors.$gray-70;
26
+ }
27
+
28
+ .totalsLabel {
29
+ @include type.type-style('label-01');
30
+ color: colors.$gray-70;
31
+ }
32
+
33
+ .totalsValue {
34
+ @include type.type-style('heading-04');
35
+ color: colors.$gray-100;
36
+ }
37
+
38
+ .headerLabelContainer {
39
+ margin-bottom: layout.$spacing-05;
40
+ }
41
+
42
+ .link {
43
+ text-decoration: none;
44
+ display: flex;
45
+ align-items: center;
46
+ color: colors.$blue-60;
47
+
48
+ svg {
49
+ margin-left: layout.$spacing-03;
50
+ }
51
+ }
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { BrowserRouter, Route, Routes } from 'react-router-dom';
3
3
  import { UserHasAccess } from '@openmrs/esm-framework';
4
- import MainComponent from './component/main.component';
5
- import { AdmittedQueue } from './tables/admitted-queue.component';
4
+ import MainComponent from './home/home.component';
5
+ import DeceasedDetailsView from './view-details/main/main.component';
6
6
 
7
7
  const Root: React.FC = () => {
8
8
  const baseName = window.getOpenmrsSpaBase() + 'home/morgue';
@@ -12,7 +12,11 @@ const Root: React.FC = () => {
12
12
  <UserHasAccess privilege="o3 : View Mortuary Dashboard">
13
13
  <Routes>
14
14
  <Route path="/" element={<MainComponent />} />
15
- <Route path="/allocation" element={<AdmittedQueue />} />
15
+ <Route
16
+ path="/patient/:patientUuid/compartment/:bedNumber/:bedId/mortuary-chart"
17
+ element={<DeceasedDetailsView />}
18
+ />
19
+ <Route path="/patient/:patientUuid/mortuary-chart" element={<DeceasedDetailsView />} />
16
20
  </Routes>
17
21
  </UserHasAccess>
18
22
  </BrowserRouter>
package/src/routes.json CHANGED
@@ -9,7 +9,6 @@
9
9
  "component": "morgueDashboardLink",
10
10
  "name": "morgue-dashboard-link",
11
11
  "slot": "homepage-dashboard-slot",
12
- "featureFlag": "mortuaryFeatureFlag",
13
12
  "meta": {
14
13
  "name": "morgue",
15
14
  "title": "morgue",
@@ -47,6 +46,11 @@
47
46
  "fullWidth": false
48
47
  }
49
48
  },
49
+ {
50
+ "name": "mortuary-summary-info",
51
+ "component": "mortuarySummary",
52
+ "slot": "mortuary-summary-info-slot"
53
+ },
50
54
  {
51
55
  "component": "root",
52
56
  "name": "morgue-dashboard-root",
@@ -82,6 +86,26 @@
82
86
  "component": "swapForm",
83
87
  "title": "Swap form",
84
88
  "type": "other-form"
89
+ },{
90
+ "name": "admit-deceased-person-form",
91
+ "component": "admitDeceasedPersonForm",
92
+ "title": "Admit Deceased Person",
93
+ "type": "other-form"
94
+ },
95
+ {
96
+ "name": "mortuary-form-entry",
97
+ "component": "mortuaryFormEntry",
98
+ "title": "Mortuary Form Entry",
99
+ "type": "form",
100
+ "width": "extra-wide",
101
+ "canMaximize": true,
102
+ "canHide": true
103
+ },
104
+ {
105
+ "name": "dispose-deceased-person-form",
106
+ "component": "disposeDeceasedPersonForm",
107
+ "title": "Dispose Deceased Person",
108
+ "type": "other-form"
85
109
  }
86
110
  ],
87
111
  "pages": [
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+
3
+ export const deceasedPatientAdmitSchema = z.object({
4
+ dateOfAdmission: z
5
+ .date({ coerce: true })
6
+ .refine((date) => !!date, 'Date of admission is required')
7
+ .refine((date) => date <= new Date(), 'Date of admission cannot be in the future'),
8
+ timeOfDeath: z.string().nonempty('Time of death is required'),
9
+ period: z
10
+ .string()
11
+ .nonempty('AM/PM is required')
12
+ .regex(/^(AM|PM)$/i, 'Invalid period'),
13
+ tagNumber: z.string().nonempty('Tag number is required'),
14
+ obNumber: z.string().optional(),
15
+ policeName: z.string().optional(),
16
+ policeIDNo: z.string().optional(),
17
+ dischargeArea: z.string().optional(),
18
+ visitType: z.string().uuid('invalid visit type'),
19
+ availableCompartment: z
20
+ .union([z.number(), z.string()])
21
+ .refine((val) => {
22
+ if (typeof val === 'string') {
23
+ return val.length > 0 && !isNaN(Number(val)) && Number(val) > 0;
24
+ }
25
+ return typeof val === 'number' && !isNaN(val) && val > 0;
26
+ }, 'Please select a valid compartment')
27
+ .transform((val) => (typeof val === 'string' ? Number(val) : val)),
28
+ services: z.array(z.string().uuid('invalid service')).nonempty('Must select one service'),
29
+ paymentMethod: z.string().uuid('invalid payment method'),
30
+ insuranceScheme: z.string().optional(),
31
+ policyNumber: z.string().optional(),
32
+ });
33
+ export const dischargeSchema = z.object({
34
+ dateOfDischarge: z.date({ coerce: true }).refine((date) => !!date, 'Date of discharge is required'),
35
+ timeOfDischarge: z.string().nonempty('Time of discharge is required'),
36
+ period: z
37
+ .string()
38
+ .nonempty('AM/PM is required')
39
+ .regex(/^(AM|PM)$/i, 'Invalid period'),
40
+ burialPermitNumber: z.string().nonempty('Burial Permit Number is required'),
41
+ nextOfKinNames: z.string().nonempty('Next of kin names is required'),
42
+ relationshipType: z.string().nonempty('Next of kin relationship is required'),
43
+ nextOfKinAddress: z.string().nonempty('Next of kin address is required'),
44
+ nextOfKinContact: z
45
+ .string()
46
+ .regex(/^\d{10}$/, 'Phone number must be exactly 10 digits')
47
+ .nonempty('Next of kin phone number is required'),
48
+ });
49
+
50
+ export const disposeSchema = z.object({
51
+ dateOfDischarge: z.date({ coerce: true }).refine((date) => !!date, 'Date of discharge is required'),
52
+ timeOfDischarge: z.string().nonempty('Time of discharge is required'),
53
+ period: z
54
+ .string()
55
+ .nonempty('AM/PM is required')
56
+ .regex(/^(AM|PM)$/i, 'Invalid period'),
57
+ serialNumber: z.string().nonempty('Serial Number is required'),
58
+ courtOrderCaseNumber: z.string().nonempty('Court Order Case Number is required'),
59
+ nextOfKinNames: z.string().nonempty('Next of kin names is required'),
60
+ relationshipType: z.string().nonempty('Next of kin relationship is required'),
61
+ nextOfKinAddress: z.string().nonempty('Next of kin address is required'),
62
+ nextOfKinContact: z
63
+ .string()
64
+ .regex(/^\d{10}$/, 'Phone number must be exactly 10 digits')
65
+ .nonempty('Next of kin phone number is required'),
66
+ });
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { DataTableSkeleton, ContentSwitcher, Switch, InlineLoading } from '@carbon/react';
3
+ import { ArrowRight } from '@carbon/react/icons';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { ConfigurableLink, ErrorState } from '@openmrs/esm-framework';
6
+ import styles from './summary.scss';
7
+ import MetricsCard from '../metrics/metrics-card.component';
8
+
9
+ interface SummaryProps {
10
+ awaitingQueueCount: number;
11
+ admittedCount: number;
12
+ dischargedCount: number;
13
+ isLoading?: boolean;
14
+ }
15
+
16
+ const Summary: React.FC<SummaryProps> = ({ awaitingQueueCount, admittedCount, dischargedCount, isLoading = false }) => {
17
+ const { t } = useTranslation();
18
+
19
+ return (
20
+ <>
21
+ <div className={styles.cardContainer}>
22
+ <MetricsCard
23
+ headerLabel={t('awaitingAdmissionHeader', 'Awaiting Admission')}
24
+ label={t('totalCount', 'total')}
25
+ value={isLoading ? <InlineLoading /> : awaitingQueueCount.toString()}
26
+ />
27
+ <MetricsCard
28
+ headerLabel={t('admittedHeader', 'Admitted')}
29
+ label={t('totalCount', 'total')}
30
+ value={admittedCount.toString()}
31
+ />
32
+ <MetricsCard
33
+ headerLabel={t('dischargedHeader', 'Discharged')}
34
+ label={t('totalCount', 'total')}
35
+ value={dischargedCount.toString()}
36
+ />
37
+ </div>
38
+ </>
39
+ );
40
+ };
41
+
42
+ export default Summary;
@@ -0,0 +1,10 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+
4
+ .cardContainer {
5
+ background-color: colors.$white-0;
6
+ display: grid;
7
+ grid-template-columns: repeat(3, minmax(0, 1fr));
8
+ justify-content: space-between;
9
+ padding: layout.$spacing-05;
10
+ }