@kenyaemr/esm-morgue-app 5.4.2-pre.2297 → 5.4.2-pre.2301

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 (38) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/dist/201.js +1 -1
  3. package/dist/201.js.map +1 -1
  4. package/dist/398.js +1 -1
  5. package/dist/398.js.map +1 -1
  6. package/dist/{826.js → 579.js} +2 -2
  7. package/dist/{826.js.map → 579.js.map} +1 -1
  8. package/dist/632.js +1 -1
  9. package/dist/632.js.map +1 -1
  10. package/dist/633.js +1 -1
  11. package/dist/633.js.map +1 -1
  12. package/dist/{146.js → 989.js} +2 -2
  13. package/dist/989.js.map +1 -0
  14. package/dist/kenyaemr-esm-morgue-app.js +1 -1
  15. package/dist/kenyaemr-esm-morgue-app.js.buildmanifest.json +73 -73
  16. package/dist/main.js +1 -1
  17. package/dist/main.js.map +1 -1
  18. package/dist/routes.json +1 -1
  19. package/package.json +1 -1
  20. package/src/bed-layout/discharged/discharged-bed-layout.component.tsx +32 -3
  21. package/src/bed-layout/discharged/discharged-bed-layout.resource.ts +22 -12
  22. package/src/bed-linelist-view/admitted/admitted-bed-linelist-view.component.tsx +1 -0
  23. package/src/bed-linelist-view/discharged/discharged-bed-line-view.component.tsx +0 -1
  24. package/src/config-schema.ts +0 -6
  25. package/src/forms/admit-deceased-person-workspace/admit-deceased-person.resource.ts +117 -75
  26. package/src/forms/admit-deceased-person-workspace/admit-deceased-person.scss +1 -1
  27. package/src/forms/admit-deceased-person-workspace/admit-deceased-person.workspace.tsx +15 -14
  28. package/src/forms/discharge-deceased-person-workspace/discharge-body.resource.ts +124 -26
  29. package/src/forms/discharge-deceased-person-workspace/discharge-body.workspace.tsx +188 -82
  30. package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.workspace.tsx +133 -48
  31. package/src/home/home.component.tsx +7 -2
  32. package/src/home/home.resource.ts +1 -1
  33. package/src/switcher/content-switcher.component.tsx +0 -1
  34. package/src/types/index.ts +1 -4
  35. package/src/utils/utils.ts +23 -39
  36. package/dist/146.js.map +0 -1
  37. /package/dist/{826.js.LICENSE.txt → 579.js.LICENSE.txt} +0 -0
  38. /package/dist/{146.js.LICENSE.txt → 989.js.LICENSE.txt} +0 -0
@@ -45,16 +45,24 @@ export const usePersonAttributes = (personUuid: string) => {
45
45
  ) => {
46
46
  const { attributeType, value } = payload;
47
47
 
48
- const existingAttribute = person.attributes.find((attr) => attr.uuid === attributeType);
49
-
50
- if (existingAttribute) {
51
- return updatePersonAttributes(
52
- { attributeType: existingAttribute.uuid, value },
53
- personUuid,
54
- existingAttribute.uuid,
55
- );
56
- } else {
57
- return createPersonAttribute({ attributeType, value }, personUuid);
48
+ const existingAttribute = person.attributes.find((attr) => attr.attributeType?.uuid === attributeType);
49
+
50
+ try {
51
+ if (existingAttribute) {
52
+ return await updatePersonAttributes({ attributeType, value }, personUuid, existingAttribute.uuid);
53
+ } else {
54
+ return await createPersonAttribute({ attributeType, value }, personUuid);
55
+ }
56
+ } catch (error) {
57
+ console.error('Error creating/updating person attribute:', error);
58
+
59
+ if (error?.responseBody?.error?.message?.includes('already in use')) {
60
+ throw new Error(
61
+ `The identifier "${value}" is already in use by another patient. Please use a different identifier.`,
62
+ );
63
+ }
64
+
65
+ throw error;
58
66
  }
59
67
  };
60
68
 
@@ -67,74 +75,164 @@ export const usePersonAttributes = (personUuid: string) => {
67
75
  createOrUpdatePersonAttribute,
68
76
  };
69
77
  };
70
-
71
78
  export const useBills = (
72
79
  patientUuid: string = '',
73
- billStatus: PaymentStatus.PENDING | '' | string = '',
80
+ billStatus: PaymentStatus | '' | string = '',
74
81
  startingDate: Date = dayjs().startOf('day').toDate(),
75
82
  endDate: Date = dayjs().endOf('day').toDate(),
76
83
  ) => {
77
84
  const startingDateISO = startingDate.toISOString();
78
85
  const endDateISO = endDate.toISOString();
79
86
 
80
- const url = `${restBaseUrl}/cashier/bill?status=${billStatus}&v=custom:(uuid,display,voided,voidReason,adjustedBy,cashPoint:(uuid,name),cashier:(uuid,display),dateCreated,lineItems,patient:(uuid,display))&createdOnOrAfter=${startingDateISO}&createdOnOrBefore=${endDateISO}`;
87
+ let url = `${restBaseUrl}/cashier/bill?v=custom:(uuid,display,voided,voidReason,adjustedBy,cashPoint:(uuid,name),cashier:(uuid,display),dateCreated,lineItems,patient:(uuid,display),status,balance,totalPayments,totalExempted,totalDeposits,closed)&createdOnOrAfter=${startingDateISO}&createdOnOrBefore=${endDateISO}`;
88
+
89
+ if (patientUuid) {
90
+ url += `&patientUuid=${patientUuid}`;
91
+ }
92
+
93
+ if (billStatus) {
94
+ url += `&status=${billStatus}`;
95
+ }
81
96
 
82
97
  const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array<PatientInvoice> } }>(
83
- patientUuid ? `${url}&patientUuid=${patientUuid}` : url,
98
+ url,
84
99
  openmrsFetch,
85
100
  {
86
101
  errorRetryCount: 2,
87
102
  },
88
103
  );
89
104
 
90
- const sortBills = sortBy(data?.data?.results ?? [], ['dateCreated']).reverse();
91
- const filteredBills = billStatus === '' ? sortBills : sortBills?.filter((bill) => bill?.status === billStatus);
92
- const filteredResults = filteredBills?.filter((res) => res.patient?.uuid === patientUuid);
93
- const formattedBills = isEmpty(patientUuid) ? filteredBills : filteredResults || [];
105
+ const results = data?.data?.results ?? [];
106
+
107
+ const sortedBills = sortBy(results, ['dateCreated']).reverse();
108
+
109
+ const patientFilteredBills = patientUuid
110
+ ? sortedBills.filter((bill) => bill.patient?.uuid === patientUuid)
111
+ : sortedBills;
112
+
113
+ const statusFilteredBills = billStatus
114
+ ? patientFilteredBills.filter((bill) => bill.status === billStatus)
115
+ : patientFilteredBills;
94
116
 
95
117
  return {
96
- bills: formattedBills,
118
+ bills: statusFilteredBills,
97
119
  error,
98
120
  isLoading,
99
121
  isValidating,
100
122
  mutate,
101
123
  };
102
124
  };
125
+
103
126
  interface UseBlockDischargeWithPendingBillsProps {
104
127
  patientUuid: string;
128
+ actionType: 'discharge' | 'dispose';
105
129
  }
106
130
 
107
131
  interface UseBlockDischargeWithPendingBillsReturn {
108
132
  isDischargeBlocked: boolean;
109
- pendingBills: any[];
133
+ pendingBills: PatientInvoice[];
110
134
  pendingBillsCount: number;
111
135
  isLoadingBills: boolean;
112
136
  billsError: any;
113
137
  blockingMessage: string;
114
138
  }
139
+
115
140
  export const useBlockDischargeWithPendingBills = ({
116
141
  patientUuid,
142
+ actionType,
117
143
  }: UseBlockDischargeWithPendingBillsProps): UseBlockDischargeWithPendingBillsReturn => {
118
- const { bills, isLoading, error } = useBills(patientUuid, PaymentStatus.PENDING);
144
+ const { bills, isLoading, isValidating, error } = useBills(patientUuid, '');
119
145
 
120
146
  const result = useMemo(() => {
121
- const pendingBills = bills?.filter((bill) => bill.status === PaymentStatus.PENDING) || [];
147
+ if (isLoading || isValidating) {
148
+ return {
149
+ isDischargeBlocked: false,
150
+ pendingBills: [],
151
+ pendingBillsCount: 0,
152
+ isLoadingBills: true,
153
+ billsError: error,
154
+ blockingMessage: '',
155
+ };
156
+ }
157
+
158
+ if (error) {
159
+ console.error('Error fetching bills:', error);
160
+ return {
161
+ isDischargeBlocked: false,
162
+ pendingBills: [],
163
+ pendingBillsCount: 0,
164
+ isLoadingBills: false,
165
+ billsError: error,
166
+ blockingMessage: '',
167
+ };
168
+ }
169
+
170
+ const pendingBills =
171
+ bills?.filter((bill) => {
172
+ const isPending = bill.status === PaymentStatus.PENDING;
173
+ const isNotVoided = !bill.voided;
174
+
175
+ let hasBalance = false;
176
+ if (bill.balance !== undefined) {
177
+ hasBalance = bill.balance > 0;
178
+ } else {
179
+ const totalAmount =
180
+ bill.lineItems?.reduce((sum, item) => {
181
+ return sum + (item as any).price * (item as any).quantity;
182
+ }, 0) || 0;
183
+
184
+ const totalPayments =
185
+ bill.totalPayments || bill.payments?.reduce((sum, payment) => sum + (payment.amount || 0), 0) || 0;
186
+
187
+ const totalExempted = bill.totalExempted || 0;
188
+ const totalDeposits = bill.totalDeposits || 0;
189
+
190
+ const outstandingBalance = totalAmount - totalPayments - totalExempted - totalDeposits;
191
+ hasBalance = outstandingBalance > 0;
192
+ }
193
+
194
+ const isNotClosed = bill.closed !== undefined ? !bill.closed : true;
195
+
196
+ return isPending && hasBalance && isNotVoided && isNotClosed;
197
+ }) || [];
198
+
122
199
  const pendingBillsCount = pendingBills.length;
123
200
  const isDischargeBlocked = pendingBillsCount > 0;
124
201
 
202
+ const totalOutstandingBalance = pendingBills.reduce((acc, bill) => {
203
+ if (bill.balance !== undefined) {
204
+ return acc + bill.balance;
205
+ } else {
206
+ const totalAmount =
207
+ bill.lineItems?.reduce((sum, item) => {
208
+ return sum + (item as any).price * (item as any).quantity;
209
+ }, 0) || 0;
210
+
211
+ const totalPayments =
212
+ bill.totalPayments || bill.payments?.reduce((sum, payment) => sum + (payment.amount || 0), 0) || 0;
213
+
214
+ const totalExempted = bill.totalExempted || 0;
215
+ const totalDeposits = bill.totalDeposits || 0;
216
+
217
+ const outstandingBalance = totalAmount - totalPayments - totalExempted - totalDeposits;
218
+ return acc + Math.max(0, outstandingBalance);
219
+ }
220
+ }, 0);
221
+
125
222
  const blockingMessage = isDischargeBlocked
126
- ? `Sorry, Pending bills must be resolved before discharging a deceased patient.
127
- `
223
+ ? `The deceased patient cannot be ${actionType}d due to ${pendingBillsCount} unpaid ${
224
+ pendingBillsCount === 1 ? 'bill' : 'bills'
225
+ } (Total balance: Ksh.${totalOutstandingBalance.toLocaleString()}). Please visit the cashier to settle all outstanding bills before ${actionType}.`
128
226
  : '';
129
227
  return {
130
228
  isDischargeBlocked,
131
229
  pendingBills,
132
230
  pendingBillsCount,
133
- isLoadingBills: isLoading,
231
+ isLoadingBills: false,
134
232
  billsError: error,
135
233
  blockingMessage,
136
234
  };
137
- }, [bills, isLoading, error]);
235
+ }, [bills, isLoading, isValidating, error, actionType]);
138
236
 
139
237
  return result;
140
238
  };
@@ -25,7 +25,7 @@ import {
25
25
  useLayoutType,
26
26
  useVisit,
27
27
  } from '@openmrs/esm-framework';
28
- import React, { useCallback, useEffect } from 'react';
28
+ import React, { useCallback, useEffect, useState } from 'react';
29
29
  import { Controller, useForm } from 'react-hook-form';
30
30
  import { useTranslation } from 'react-i18next';
31
31
  import { mutate as mutateSWR } from 'swr';
@@ -33,7 +33,7 @@ import { z } from 'zod';
33
33
  import styles from './discharge-body.scss';
34
34
  import DeceasedInfo from '../../deceased-patient-header/deceasedInfo/deceased-info.component';
35
35
  import { PatientInfo } from '../../types';
36
- import { useBills, useBlockDischargeWithPendingBills, usePersonAttributes } from './discharge-body.resource';
36
+ import { useBlockDischargeWithPendingBills, usePersonAttributes } from './discharge-body.resource';
37
37
  import { ConfigObject } from '../../config-schema';
38
38
  import { getCurrentTime } from '../../utils/utils';
39
39
  import { dischargeSchema } from '../../schemas';
@@ -44,47 +44,66 @@ import { useMortuaryOperation } from '../admit-deceased-person-workspace/admit-d
44
44
  interface DischargeFormProps {
45
45
  closeWorkspace: () => void;
46
46
  patientUuid: string;
47
- personUuid: string;
48
47
  bedId: number;
49
48
  mutate: () => void;
50
49
  }
51
50
 
52
51
  type DischargeFormValues = z.infer<typeof dischargeSchema>;
53
52
 
54
- const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUuid, bedId, personUuid, mutate }) => {
53
+ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUuid, bedId, mutate }) => {
55
54
  const { t } = useTranslation();
56
55
  const isTablet = useLayoutType() === 'tablet';
56
+ const [submissionError, setSubmissionError] = useState<string | null>(null);
57
+
57
58
  const { activeVisit, currentVisitIsRetrospective } = useVisit(patientUuid);
58
59
  const { queueEntry } = useVisitQueueEntry(patientUuid, activeVisit?.uuid);
60
+
59
61
  const { dischargeBody, isLoadingEmrConfiguration } = useMortuaryOperation();
60
- const { createOrUpdatePersonAttribute, personAttributes } = usePersonAttributes(personUuid);
62
+
63
+ const {
64
+ createOrUpdatePersonAttribute,
65
+ personAttributes,
66
+ isLoading: isLoadingAttributes,
67
+ } = usePersonAttributes(patientUuid);
68
+
61
69
  const { isDischargeBlocked, blockingMessage, isLoadingBills } = useBlockDischargeWithPendingBills({
62
70
  patientUuid,
71
+ actionType: 'discharge',
63
72
  });
64
73
 
65
- const { time: defaultTime, period: defaultPeriod } = getCurrentTime();
66
-
67
- const { nextOfKinAddressUuid, nextOfKinNameUuid, nextOfKinPhoneUuid, nextOfKinRelationshipUuid } =
74
+ const { nextOfKinNameUuid, nextOfKinRelationshipUuid, nextOfKinPhoneUuid, nextOfKinAddressUuid } =
68
75
  useConfig<ConfigObject>();
69
76
 
77
+ const { time: defaultTime, period: defaultPeriod } = getCurrentTime();
78
+
70
79
  const getAttributeValue = useCallback(
71
80
  (attributeTypeUuid: string) => {
72
- if (!personAttributes) {
81
+ if (!personAttributes || !Array.isArray(personAttributes)) {
73
82
  return '';
74
83
  }
75
- const attributes = Array.isArray(personAttributes) ? personAttributes : [];
76
- const attribute = attributes.find((attr) => attr.attributeType.uuid === attributeTypeUuid);
84
+ const attribute = personAttributes.find((attr) => attr.attributeType.uuid === attributeTypeUuid);
77
85
  return attribute ? attribute.value : '';
78
86
  },
79
87
  [personAttributes],
80
88
  );
81
89
 
90
+ const getExistingAttributeUuid = useCallback(
91
+ (attributeTypeUuid: string) => {
92
+ if (!personAttributes || !Array.isArray(personAttributes)) {
93
+ return null;
94
+ }
95
+ const attribute = personAttributes.find((attr) => attr.attributeType.uuid === attributeTypeUuid);
96
+ return attribute ? attribute.uuid : null;
97
+ },
98
+ [personAttributes],
99
+ );
100
+
82
101
  const {
83
- watch,
84
102
  control,
85
103
  setValue,
86
104
  handleSubmit,
87
105
  formState: { errors, isDirty, isSubmitting },
106
+ watch,
88
107
  } = useForm<DischargeFormValues>({
89
108
  resolver: zodResolver(dischargeSchema),
90
109
  defaultValues: {
@@ -92,96 +111,176 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
92
111
  timeOfDischarge: defaultTime,
93
112
  period: defaultPeriod,
94
113
  burialPermitNumber: '',
114
+ nextOfKinNames: '',
115
+ relationshipType: '',
116
+ nextOfKinContact: '',
117
+ nextOfKinAddress: '',
95
118
  },
96
119
  });
120
+
97
121
  useEffect(() => {
98
122
  if (Array.isArray(personAttributes) && personAttributes.length > 0) {
99
- setValue('nextOfKinNames', getAttributeValue(nextOfKinNameUuid));
100
- setValue('relationshipType', getAttributeValue(nextOfKinRelationshipUuid));
101
- setValue('nextOfKinContact', getAttributeValue(nextOfKinPhoneUuid));
102
- setValue('nextOfKinAddress', getAttributeValue(nextOfKinAddressUuid));
123
+ const initialValues = {
124
+ nextOfKinNames: getAttributeValue(nextOfKinNameUuid),
125
+ relationshipType: getAttributeValue(nextOfKinRelationshipUuid),
126
+ nextOfKinContact: getAttributeValue(nextOfKinPhoneUuid),
127
+ nextOfKinAddress: getAttributeValue(nextOfKinAddressUuid),
128
+ };
129
+
130
+ Object.entries(initialValues).forEach(([field, value]) => {
131
+ if (value !== undefined && value !== null && value !== '') {
132
+ setValue(field as keyof DischargeFormValues, value);
133
+ }
134
+ });
103
135
  }
104
136
  }, [
105
137
  personAttributes,
138
+ setValue,
106
139
  getAttributeValue,
107
140
  nextOfKinNameUuid,
108
141
  nextOfKinRelationshipUuid,
109
142
  nextOfKinPhoneUuid,
110
143
  nextOfKinAddressUuid,
111
- setValue,
112
144
  ]);
145
+
113
146
  const onSubmit = async (data: DischargeFormValues) => {
147
+ setSubmissionError(null);
148
+
114
149
  if (currentVisitIsRetrospective) {
115
150
  setCurrentVisit(null, null);
116
151
  closeWorkspace();
117
- } else {
118
- try {
119
- const nextOfKinAttributes = [
120
- { attributeType: nextOfKinNameUuid, value: data.nextOfKinNames },
121
- { attributeType: nextOfKinRelationshipUuid, value: data.relationshipType },
122
- { attributeType: nextOfKinPhoneUuid, value: data.nextOfKinContact },
123
- { attributeType: nextOfKinAddressUuid, value: data.nextOfKinAddress },
124
- ];
125
- const patientInfo: PatientInfo = {
126
- uuid: activeVisit.patient.uuid,
127
- attributes: (activeVisit?.patient?.person?.attributes || []).map((attr) => ({
128
- uuid: attr.uuid,
129
- display: attr.display || '',
130
- })),
131
- };
132
-
133
- for (const attribute of nextOfKinAttributes) {
134
- await createOrUpdatePersonAttribute(patientUuid, attribute, patientInfo);
152
+ return;
153
+ }
154
+
155
+ try {
156
+ // First, perform the discharge operation
157
+ await dischargeBody(activeVisit, queueEntry, bedId, data);
158
+
159
+ // Then update person attributes
160
+ const attributeUpdates = [
161
+ {
162
+ uuid: nextOfKinNameUuid,
163
+ value: data.nextOfKinNames,
164
+ existingUuid: getExistingAttributeUuid(nextOfKinNameUuid),
165
+ },
166
+ {
167
+ uuid: nextOfKinRelationshipUuid,
168
+ value: data.relationshipType,
169
+ existingUuid: getExistingAttributeUuid(nextOfKinRelationshipUuid),
170
+ },
171
+ {
172
+ uuid: nextOfKinPhoneUuid,
173
+ value: data.nextOfKinContact,
174
+ existingUuid: getExistingAttributeUuid(nextOfKinPhoneUuid),
175
+ },
176
+ {
177
+ uuid: nextOfKinAddressUuid,
178
+ value: data.nextOfKinAddress,
179
+ existingUuid: getExistingAttributeUuid(nextOfKinAddressUuid),
180
+ },
181
+ ].filter((attr) => attr.value !== undefined && attr.value !== null && attr.value !== '');
182
+
183
+ const patientInfo: PatientInfo = {
184
+ uuid: activeVisit.patient.uuid,
185
+ attributes: personAttributes || [],
186
+ };
187
+
188
+ for (const attr of attributeUpdates) {
189
+ try {
190
+ const attributeData: any = {
191
+ attributeType: attr.uuid,
192
+ value: attr.value,
193
+ };
194
+
195
+ if (attr.existingUuid) {
196
+ attributeData.uuid = attr.existingUuid;
197
+ }
198
+
199
+ await createOrUpdatePersonAttribute(patientUuid, attributeData, patientInfo);
200
+ } catch (error) {
201
+ showSnackbar({
202
+ title: t('errorUpdatingAttribute', 'Error Updating Attribute'),
203
+ subtitle: t('errorUpdatingAttributeDescription', 'An error occurred while updating the attribute'),
204
+ kind: 'error',
205
+ isLowContrast: true,
206
+ });
135
207
  }
208
+ }
209
+
210
+ // Invalidate relevant caches
211
+ await Promise.all([
212
+ mutateSWR((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/visit`)),
213
+ mutateSWR((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/patient`)),
214
+ mutateSWR((key) => typeof key === 'string' && key.startsWith(`${fhirBaseUrl}/Encounter`)),
215
+ ]);
216
+
217
+ showSnackbar({
218
+ title: t('dischargeDeceasedPatient', 'Deceased patient discharged'),
219
+ subtitle: t('deceasedPatientDischargedSuccessfully', 'Deceased patient has been discharged successfully'),
220
+ kind: 'success',
221
+ isLowContrast: true,
222
+ });
136
223
 
137
- await dischargeBody(activeVisit, queueEntry, bedId, data);
138
-
139
- showSnackbar({
140
- title: t('dischargeDeceasedPatient', 'Deceased patient'),
141
- subtitle: t('deceasedPatientDischargedSuccessfully', 'Deceased patient has been discharged successfully'),
142
- kind: 'success',
143
- isLowContrast: true,
144
- });
145
- mutateSWR((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/visit`));
146
- mutateSWR((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/patient`));
147
- mutateSWR((key) => typeof key === 'string' && key.startsWith(`${fhirBaseUrl}/Encounter`));
148
-
149
- mutate();
150
- closeWorkspace();
151
- } catch (error) {
152
- console.error(error);
153
- const errorMessage = JSON.stringify(error?.responseBody?.error?.message?.replace(/\[/g, '').replace(/\]/g, ''));
154
- showSnackbar({
155
- title: t('visitError', 'Visit Error'),
156
- subtitle: t(
157
- 'visitErrorMessage',
158
- `An error has occurred while ending visit, Contact system administrator quoting this error ${errorMessage}`,
159
- ),
160
- kind: 'error',
161
- isLowContrast: true,
162
- });
224
+ mutate();
225
+ closeWorkspace();
226
+ } catch (error) {
227
+ let errorMessage = t('dischargeUnknownError', 'An unknown error occurred');
228
+ if (error?.message) {
229
+ errorMessage = error.message;
230
+ } else if (error?.responseBody?.error?.message) {
231
+ errorMessage = error.responseBody.error.message.replace(/\[|\]/g, '');
232
+ } else if (error?.responseBody?.error?.globalErrors) {
233
+ errorMessage = error.responseBody.error.globalErrors[0]?.message || errorMessage;
163
234
  }
235
+
236
+ setSubmissionError(errorMessage);
237
+ showSnackbar({
238
+ title: t('dischargeError', 'Discharge Error'),
239
+ subtitle: errorMessage,
240
+ kind: 'error',
241
+ isLowContrast: true,
242
+ });
164
243
  }
165
244
  };
166
245
 
167
- if (isLoadingEmrConfiguration || !personAttributes) {
168
- return <InlineLoading status="active" iconDescription="Loading" description="Loading ..." />;
246
+ if (isLoadingEmrConfiguration || isLoadingAttributes || !activeVisit) {
247
+ return <InlineLoading status="active" iconDescription="Loading" description={t('loading', 'Loading...')} />;
169
248
  }
170
249
 
171
250
  return (
172
251
  <Form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
173
252
  <div className={styles.formContainer}>
253
+ {isLoadingBills && (
254
+ <InlineLoading
255
+ status="active"
256
+ iconDescription="Loading"
257
+ description={t('loadingBills', 'Loading bills...')}
258
+ />
259
+ )}
260
+
261
+ {isDischargeBlocked && (
262
+ <InlineNotification
263
+ kind="warning"
264
+ title={t('warningMsg', 'Warning')}
265
+ subtitle={blockingMessage}
266
+ lowContrast={true}
267
+ className={styles.blockingNotification}
268
+ />
269
+ )}
270
+
271
+ {submissionError && (
272
+ <InlineNotification
273
+ kind="error"
274
+ title={t('error', 'Error')}
275
+ subtitle={submissionError}
276
+ lowContrast={true}
277
+ className={styles.errorNotification}
278
+ />
279
+ )}
280
+
174
281
  <Stack gap={3}>
175
282
  <DeceasedInfo patientUuid={patientUuid} />
176
- {isDischargeBlocked && (
177
- <InlineNotification
178
- kind="error"
179
- title={t('dischargeBlocked', 'Discharge Blocked')}
180
- subtitle={blockingMessage}
181
- lowContrast={true}
182
- className={styles.blockingNotification}
183
- />
184
- )}
283
+
185
284
  <ResponsiveWrapper>
186
285
  <div className={styles.dateTimePickerContainer}>
187
286
  <Column>
@@ -200,9 +299,9 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
200
299
  value={field.value ? new Date(field.value) : null}>
201
300
  <DatePickerInput
202
301
  {...field}
203
- id="date-of-admission"
302
+ id="date-of-discharge"
204
303
  placeholder="yyyy-mm-dd"
205
- labelText={t('dateOfAdmission', 'Date of discharge*')}
304
+ labelText={t('dateOfDischarge', 'Date of discharge*')}
206
305
  invalid={!!errors.dateOfDischarge}
207
306
  invalidText={errors.dateOfDischarge?.message}
208
307
  />
@@ -245,6 +344,7 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
245
344
  </div>
246
345
  </Column>
247
346
  </div>
347
+
248
348
  <Column className={styles.fieldColumn}>
249
349
  <Controller
250
350
  name="burialPermitNumber"
@@ -263,6 +363,7 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
263
363
  )}
264
364
  />
265
365
  </Column>
366
+
266
367
  <Column className={styles.fieldColumn}>
267
368
  <Controller
268
369
  name="nextOfKinNames"
@@ -281,6 +382,7 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
281
382
  )}
282
383
  />
283
384
  </Column>
385
+
284
386
  <Column className={styles.fieldColumn}>
285
387
  <Controller
286
388
  name="relationshipType"
@@ -288,17 +390,18 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
288
390
  render={({ field }) => (
289
391
  <TextInput
290
392
  {...field}
291
- id="relationship"
393
+ id="relationshipType"
292
394
  type="text"
293
395
  className={styles.sectionField}
294
- placeholder={t('relationship', 'Relationship')}
295
- labelText={t('relationship', 'Relationship')}
396
+ placeholder={t('relationshipType', 'Relationship')}
397
+ labelText={t('relationshipType', 'Relationship')}
296
398
  invalid={!!errors.relationshipType}
297
399
  invalidText={errors.relationshipType?.message}
298
400
  />
299
401
  )}
300
402
  />
301
403
  </Column>
404
+
302
405
  <Column className={styles.fieldColumn}>
303
406
  <Controller
304
407
  name="nextOfKinContact"
@@ -306,17 +409,18 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
306
409
  render={({ field }) => (
307
410
  <TextInput
308
411
  {...field}
309
- id="telephone"
412
+ id="nextOfKinContact"
310
413
  type="text"
311
- className={styles.fieldSection}
312
- placeholder={t('telephone', 'Telephone number')}
313
- labelText={t('telephone', 'Telephone number')}
414
+ className={styles.sectionField}
415
+ placeholder={t('nextOfKinContact', 'Next of kin contact')}
416
+ labelText={t('nextOfKinContact', 'Next of kin contact')}
314
417
  invalid={!!errors.nextOfKinContact}
315
418
  invalidText={errors.nextOfKinContact?.message}
316
419
  />
317
420
  )}
318
421
  />
319
422
  </Column>
423
+
320
424
  <Column className={styles.fieldColumn}>
321
425
  <Controller
322
426
  name="nextOfKinAddress"
@@ -336,6 +440,7 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
336
440
  />
337
441
  </Column>
338
442
  </ResponsiveWrapper>
443
+
339
444
  <ResponsiveWrapper>
340
445
  <Column>
341
446
  <ExtensionSlot
@@ -349,6 +454,7 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
349
454
  </ResponsiveWrapper>
350
455
  </Stack>
351
456
  </div>
457
+
352
458
  <ButtonSet
353
459
  className={classNames({
354
460
  [styles.tablet]: isTablet,
@@ -364,7 +470,7 @@ const DischargeForm: React.FC<DischargeFormProps> = ({ closeWorkspace, patientUu
364
470
  type="submit">
365
471
  {isSubmitting ? (
366
472
  <span className={styles.inlineLoading}>
367
- {t('submitting', 'Submitting' + '...')}
473
+ {t('submitting', 'Submitting...')}
368
474
  <InlineLoading status="active" iconDescription="Loading" />
369
475
  </span>
370
476
  ) : (