@ampath/esm-dha-workflow-app 4.0.0-claim-workflows.428 → 4.0.0-claim-workflows.430
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.
- package/dist/{6034.js → 1392.js} +1 -1
- package/dist/{6034.js.map → 1392.js.map} +1 -1
- package/dist/4746.js +1 -0
- package/dist/4746.js.map +1 -0
- package/dist/52.js +1 -1
- package/dist/52.js.map +1 -1
- package/dist/6055.js.map +1 -1
- package/dist/7633.js +1 -1
- package/dist/esm-dha-workflow-app.js +1 -1
- package/dist/esm-dha-workflow-app.js.buildmanifest.json +58 -58
- package/dist/main.js +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/billing/dashboard/v2/patient-bill-details/attachments/death-notification.component.tsx +162 -0
- package/src/billing/dashboard/v2/patient-bill-details/attachments/death-notification.resource.ts +112 -0
- package/src/billing/dashboard/v2/patient-bill-details/attachments/death-notification.scss +88 -0
- package/src/billing/dashboard/v2/patient-bill-details/attachments/generate-attachments.component.tsx +171 -122
- package/src/billing/dashboard/v2/types/index.ts +1 -0
- package/src/billing/dashboard/v3/claim-visits/claim-intervention-details/claim-intervention-details.component.tsx +3 -1
- package/src/billing/dashboard/v3/claim-visits/claim-visit-details/claim-visit-details.component.tsx +1 -0
- package/src/billing/dashboard/v3/patient-bill-details/attachments/generate-attachments.component.tsx +57 -4
- package/src/billing/dashboard/v3/types/index.ts +1 -0
- package/dist/7376.js +0 -1
- package/dist/7376.js.map +0 -1
package/src/billing/dashboard/v2/patient-bill-details/attachments/death-notification.component.tsx
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import styles from './death-notification.scss';
|
|
3
|
+
|
|
4
|
+
export interface DeathNotificationObservation {
|
|
5
|
+
concept?: { display?: string; name?: { name?: string } };
|
|
6
|
+
value?: string | number | { display?: string; name?: { name?: string } } | null;
|
|
7
|
+
valueText?: string;
|
|
8
|
+
valueNumeric?: number;
|
|
9
|
+
valueDatetime?: string;
|
|
10
|
+
valueCoded?: { display?: string; name?: { name?: string } } | string;
|
|
11
|
+
valueCodedName?: { name?: string; display?: string };
|
|
12
|
+
voided?: boolean;
|
|
13
|
+
groupMembers?: DeathNotificationObservation[];
|
|
14
|
+
obs?: DeathNotificationObservation[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DeathNotificationEncounter {
|
|
18
|
+
encounterDatetime?: string;
|
|
19
|
+
location?: { display?: string };
|
|
20
|
+
provider?: { display?: string; person?: { display?: string } };
|
|
21
|
+
obs?: DeathNotificationObservation[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DeathNotificationPatient {
|
|
25
|
+
person?: {
|
|
26
|
+
display?: string;
|
|
27
|
+
gender?: string;
|
|
28
|
+
birthdate?: string;
|
|
29
|
+
age?: number;
|
|
30
|
+
};
|
|
31
|
+
identifiers?: Array<{
|
|
32
|
+
identifier?: string;
|
|
33
|
+
identifierType?: { display?: string };
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface DeathNotificationProps {
|
|
38
|
+
patient?: DeathNotificationPatient;
|
|
39
|
+
encounter?: DeathNotificationEncounter;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const getObservationValue = (observation: DeathNotificationObservation): string => {
|
|
43
|
+
const codedValue = observation.valueCoded ?? observation.value;
|
|
44
|
+
if (typeof codedValue === 'object' && codedValue) {
|
|
45
|
+
return codedValue.display ?? codedValue.name?.name ?? observation.valueCodedName?.display ?? observation.valueCodedName?.name ?? '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const value = observation.valueText ?? observation.valueDatetime ?? observation.valueNumeric ?? codedValue;
|
|
49
|
+
return value == null ? '' : String(value);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const flattenObservations = (observations: DeathNotificationObservation[] = []): DeathNotificationObservation[] =>
|
|
53
|
+
observations?.flatMap((observation) => {
|
|
54
|
+
if (!observation || observation.voided) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// , ...flattenObservations(observation.groupMembers), ...flattenObservations(observation.obs)
|
|
59
|
+
|
|
60
|
+
return [observation];
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const DeathNotification = forwardRef<HTMLDivElement, DeathNotificationProps>(({ patient, encounter }, ref) => {
|
|
64
|
+
const person = patient?.person;
|
|
65
|
+
const identifiers = (patient?.identifiers ?? []).filter(
|
|
66
|
+
(identifier) =>
|
|
67
|
+
identifier.identifier && identifier.identifierType?.display?.trim().toLowerCase() !== 'amrs universal id',
|
|
68
|
+
);
|
|
69
|
+
const observations = flattenObservations(encounter?.obs).filter((observation) => getObservationValue(observation));
|
|
70
|
+
const formatDate = (date?: string) => (date ? new Date(date).toLocaleDateString() : '—');
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
ref={ref}
|
|
75
|
+
className={styles.dnRoot}
|
|
76
|
+
>
|
|
77
|
+
<header className={styles.dnHeader}>
|
|
78
|
+
<div className={styles.dnEyebrow}>
|
|
79
|
+
PATIENT RECORD
|
|
80
|
+
</div>
|
|
81
|
+
<h1 className={styles.dnTitle}>Death Notification</h1>
|
|
82
|
+
<div className={styles.dnSubtitle}>
|
|
83
|
+
Generated from the completed death reporting form
|
|
84
|
+
</div>
|
|
85
|
+
</header>
|
|
86
|
+
|
|
87
|
+
<section className={styles.dnSection}>
|
|
88
|
+
<h2 className={styles.dnSectionTitle}>Patient details</h2>
|
|
89
|
+
<table className={`${styles.dnTable} ${styles.dnPatientTable}`}>
|
|
90
|
+
<tbody>
|
|
91
|
+
<tr>
|
|
92
|
+
<th>Patient name</th>
|
|
93
|
+
<td>{person?.display ?? '—'}</td>
|
|
94
|
+
<th>Gender</th>
|
|
95
|
+
<td>{person?.gender ?? '—'}</td>
|
|
96
|
+
</tr>
|
|
97
|
+
<tr>
|
|
98
|
+
<th>Date of birth</th>
|
|
99
|
+
<td>{formatDate(person?.birthdate)}</td>
|
|
100
|
+
<th>Age</th>
|
|
101
|
+
<td>{person?.age ?? '—'}</td>
|
|
102
|
+
</tr>
|
|
103
|
+
{identifiers.map((identifier, index) => (
|
|
104
|
+
<tr key={`${identifier.identifierType?.display ?? 'identifier'}-${identifier.identifier}-${index}`}>
|
|
105
|
+
<th>{identifier.identifierType?.display ?? 'Identifier'}</th>
|
|
106
|
+
<td colSpan={3}>{identifier.identifier}</td>
|
|
107
|
+
</tr>
|
|
108
|
+
))}
|
|
109
|
+
</tbody>
|
|
110
|
+
</table>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section className={styles.dnSection}>
|
|
114
|
+
<h2 className={styles.dnSectionTitle}>Death reporting details</h2>
|
|
115
|
+
<table className={styles.dnTable}>
|
|
116
|
+
<tbody>
|
|
117
|
+
<tr>
|
|
118
|
+
<th>Reported on</th>
|
|
119
|
+
<td>{formatDate(encounter?.encounterDatetime)}</td>
|
|
120
|
+
<th>Facility</th>
|
|
121
|
+
<td>{encounter?.location?.display ?? '—'}</td>
|
|
122
|
+
</tr>
|
|
123
|
+
<tr>
|
|
124
|
+
<th>Recorded by</th>
|
|
125
|
+
<td colSpan={3}>{encounter?.provider?.person?.display ?? encounter?.provider?.display ?? '—'}</td>
|
|
126
|
+
</tr>
|
|
127
|
+
</tbody>
|
|
128
|
+
</table>
|
|
129
|
+
|
|
130
|
+
{observations.length > 0 ? (
|
|
131
|
+
<table className={styles.dnTable}>
|
|
132
|
+
<thead>
|
|
133
|
+
<tr>
|
|
134
|
+
<th>Form field</th>
|
|
135
|
+
<th>Recorded information</th>
|
|
136
|
+
</tr>
|
|
137
|
+
</thead>
|
|
138
|
+
<tbody>
|
|
139
|
+
{observations.map((observation, index) => (
|
|
140
|
+
<tr key={`${observation.concept?.display ?? 'field'}-${index}`}>
|
|
141
|
+
<td>{observation.concept?.display ?? observation.concept?.name?.name ?? 'Form field'}</td>
|
|
142
|
+
<td>{getObservationValue(observation)}</td>
|
|
143
|
+
</tr>
|
|
144
|
+
))}
|
|
145
|
+
</tbody>
|
|
146
|
+
</table>
|
|
147
|
+
) : (
|
|
148
|
+
<p className={styles.dnEmptyMessage}>No additional form details were recorded.</p>
|
|
149
|
+
)}
|
|
150
|
+
</section>
|
|
151
|
+
|
|
152
|
+
<footer className={styles.dnFooter}>
|
|
153
|
+
<span className={styles.dnSystemGenerated}>System Generated</span>{' '}
|
|
154
|
+
This document contains patient health information. Handle and share it in accordance with facility policy.
|
|
155
|
+
</footer>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
DeathNotification.displayName = 'DeathNotification';
|
|
161
|
+
|
|
162
|
+
export default DeathNotification;
|
package/src/billing/dashboard/v2/patient-bill-details/attachments/death-notification.resource.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { openmrsFetch, restBaseUrl, showSnackbar, useConfig } from '@openmrs/esm-framework';
|
|
3
|
+
import { type ConfigObject } from '../../../../../config-schema';
|
|
4
|
+
import { useFormEncounters } from '../../../../../death-reporting/death-reporting-form-button.resource';
|
|
5
|
+
import { fetchPatientEncountersByType } from '../../../../../shared/services/encounters.resource';
|
|
6
|
+
import {
|
|
7
|
+
type DeathNotificationEncounter,
|
|
8
|
+
type DeathNotificationPatient,
|
|
9
|
+
} from './death-notification.component';
|
|
10
|
+
|
|
11
|
+
interface DeathNotificationData {
|
|
12
|
+
patient?: DeathNotificationPatient;
|
|
13
|
+
encounter?: DeathNotificationEncounter;
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const useDeathNotification = (
|
|
18
|
+
patientUuid?: string,
|
|
19
|
+
requireDeceasedObservation = false,
|
|
20
|
+
): DeathNotificationData => {
|
|
21
|
+
const { formEncounterTypes } = useConfig<ConfigObject>();
|
|
22
|
+
const {
|
|
23
|
+
hasDeceasedObservation,
|
|
24
|
+
hasDeathReportingFormEncounter,
|
|
25
|
+
isLoading: isLoadingDeathStatus,
|
|
26
|
+
} = useFormEncounters(patientUuid ?? '');
|
|
27
|
+
const [patient, setPatient] = useState<DeathNotificationPatient>();
|
|
28
|
+
const [encounter, setEncounter] = useState<DeathNotificationEncounter>();
|
|
29
|
+
const [isLoadingData, setIsLoadingData] = useState(false);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (
|
|
33
|
+
isLoadingDeathStatus ||
|
|
34
|
+
!hasDeathReportingFormEncounter ||
|
|
35
|
+
(requireDeceasedObservation && !hasDeceasedObservation) ||
|
|
36
|
+
!patientUuid ||
|
|
37
|
+
!formEncounterTypes.deathReportingEncounterTypeUuid
|
|
38
|
+
) {
|
|
39
|
+
setPatient(undefined);
|
|
40
|
+
setEncounter(undefined);
|
|
41
|
+
setIsLoadingData(false);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let isCurrent = true;
|
|
46
|
+
setIsLoadingData(true);
|
|
47
|
+
setPatient(undefined);
|
|
48
|
+
setEncounter(undefined);
|
|
49
|
+
|
|
50
|
+
Promise.all([
|
|
51
|
+
fetchPatientEncountersByType(patientUuid, formEncounterTypes.deathReportingEncounterTypeUuid),
|
|
52
|
+
openmrsFetch(`${restBaseUrl}/patient/${patientUuid}?v=full`),
|
|
53
|
+
])
|
|
54
|
+
.then(([encounters, patientResponse]) => {
|
|
55
|
+
if (!isCurrent) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const latestEncounter = ((encounters ?? []) as Array<DeathNotificationEncounter & { voided?: boolean }>)
|
|
60
|
+
.filter((item: DeathNotificationEncounter & { voided?: boolean }) => !item.voided)
|
|
61
|
+
.sort(
|
|
62
|
+
(left: DeathNotificationEncounter, right: DeathNotificationEncounter) =>
|
|
63
|
+
new Date(right.encounterDatetime ?? 0).getTime() - new Date(left.encounterDatetime ?? 0).getTime(),
|
|
64
|
+
)[0] as DeathNotificationEncounter | undefined;
|
|
65
|
+
const patientData = patientResponse?.data as DeathNotificationPatient | undefined;
|
|
66
|
+
|
|
67
|
+
if (!latestEncounter || !patientData) {
|
|
68
|
+
showSnackbar({
|
|
69
|
+
kind: 'error',
|
|
70
|
+
title: 'Death Notification unavailable',
|
|
71
|
+
subtitle: 'Could not load the completed death reporting form and patient details.',
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setPatient(patientData);
|
|
77
|
+
setEncounter(latestEncounter);
|
|
78
|
+
})
|
|
79
|
+
.catch((error) => {
|
|
80
|
+
console.error('Failed to load death notification details', error);
|
|
81
|
+
if (isCurrent) {
|
|
82
|
+
showSnackbar({
|
|
83
|
+
kind: 'error',
|
|
84
|
+
title: 'Death Notification unavailable',
|
|
85
|
+
subtitle: 'Could not load the completed death reporting form and patient details.',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
.finally(() => {
|
|
90
|
+
if (isCurrent) {
|
|
91
|
+
setIsLoadingData(false);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return () => {
|
|
96
|
+
isCurrent = false;
|
|
97
|
+
};
|
|
98
|
+
}, [
|
|
99
|
+
formEncounterTypes.deathReportingEncounterTypeUuid,
|
|
100
|
+
hasDeceasedObservation,
|
|
101
|
+
hasDeathReportingFormEncounter,
|
|
102
|
+
isLoadingDeathStatus,
|
|
103
|
+
patientUuid,
|
|
104
|
+
requireDeceasedObservation,
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
patient,
|
|
109
|
+
encounter,
|
|
110
|
+
isLoading: isLoadingDeathStatus || isLoadingData,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.dnRoot {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
width: 760px;
|
|
4
|
+
padding: 40px;
|
|
5
|
+
color: #161616;
|
|
6
|
+
background: #ffffff;
|
|
7
|
+
font-family: Arial, sans-serif;
|
|
8
|
+
|
|
9
|
+
*,
|
|
10
|
+
*::before,
|
|
11
|
+
*::after {
|
|
12
|
+
box-sizing: inherit;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.dnHeader {
|
|
17
|
+
border-bottom: 3px solid #da1e28;
|
|
18
|
+
margin-bottom: 28px;
|
|
19
|
+
padding-bottom: 16px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dnEyebrow {
|
|
23
|
+
color: #da1e28;
|
|
24
|
+
font-size: 13px;
|
|
25
|
+
font-weight: 700;
|
|
26
|
+
letter-spacing: 1px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dnTitle {
|
|
30
|
+
font-size: 28px;
|
|
31
|
+
margin: 8px 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.dnSubtitle {
|
|
35
|
+
color: #525252;
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.dnSection {
|
|
40
|
+
margin-bottom: 28px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dnSectionTitle {
|
|
44
|
+
font-size: 18px;
|
|
45
|
+
margin-bottom: 14px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.dnTable {
|
|
49
|
+
width: 100%;
|
|
50
|
+
border-collapse: collapse;
|
|
51
|
+
font-size: 14px;
|
|
52
|
+
margin-top: 12px;
|
|
53
|
+
|
|
54
|
+
th,
|
|
55
|
+
td {
|
|
56
|
+
border: 1px solid #d1d1d1;
|
|
57
|
+
padding: 9px;
|
|
58
|
+
text-align: left;
|
|
59
|
+
vertical-align: top;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
th {
|
|
63
|
+
width: 20%;
|
|
64
|
+
background: #f4f4f4;
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dnPatientTable {
|
|
70
|
+
margin-top: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.dnEmptyMessage {
|
|
74
|
+
color: #525252;
|
|
75
|
+
font-size: 14px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.dnFooter {
|
|
79
|
+
border-top: 1px solid #a8a8a8;
|
|
80
|
+
color: #525252;
|
|
81
|
+
font-size: 12px;
|
|
82
|
+
padding-top: 12px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.dnSystemGenerated {
|
|
86
|
+
font-weight: 700;
|
|
87
|
+
font-style: italic;
|
|
88
|
+
}
|