fhir-react 0.2.4 → 0.3.0
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/.github/workflows/publish_npmjs.yml +18 -0
- package/.storybook/config.js +9 -3
- package/.storybook/presets.js +1 -0
- package/.storybook/preview-head.html +4 -0
- package/README.md +47 -3
- package/package.json +13 -4
- package/src/assets/common/chevron-right.svg +3 -0
- package/src/assets/containers/AllergyIntolerance/allergy-intolerance.svg +9 -0
- package/src/assets/containers/Appointment/appointment.svg +14 -0
- package/src/assets/containers/CarePlan/care-plan.svg +10 -0
- package/src/assets/containers/CareTeam/care-team.svg +10 -0
- package/src/assets/containers/Claim/claim.svg +6 -0
- package/src/assets/containers/ClaimResponse/claim-response.svg +7 -0
- package/src/assets/containers/Condition/condition.svg +11 -0
- package/src/assets/containers/Device/device.svg +8 -0
- package/src/assets/containers/DiagnosticReport/diagnostic-report.svg +14 -0
- package/src/assets/containers/DocumentReference/document-reference.svg +10 -0
- package/src/assets/containers/Encounter/encounter.svg +10 -0
- package/src/assets/containers/ExplanationOfBenefit/explanation-of-benefit.svg +3 -0
- package/src/assets/containers/FamilyMemberHistory/family-member-history.svg +7 -0
- package/src/assets/containers/Goal/goal.svg +11 -0
- package/src/assets/containers/Immunization/immunization.svg +7 -0
- package/src/assets/containers/List/list.svg +3 -0
- package/src/assets/containers/Location/location.svg +4 -0
- package/src/assets/containers/Medication/medication.svg +5 -0
- package/src/assets/containers/MedicationAdministration/medication-administration.svg +6 -0
- package/src/assets/containers/MedicationKnowledge/medication-knowledge.svg +11 -0
- package/src/assets/containers/MedicationStatement/medication-statement.svg +5 -0
- package/src/assets/containers/Observation/observation.svg +12 -0
- package/src/assets/containers/Practitioner/practitioner.svg +5 -0
- package/src/assets/containers/Procedure/procedure.svg +9 -0
- package/src/assets/containers/Questionnaire/questionnaire.svg +6 -0
- package/src/assets/containers/QuestionnaireResponse/questionnaire-response.svg +6 -0
- package/src/assets/containers/QustionnaireResponse/questionnaire-response.svg +6 -0
- package/src/assets/containers/ResearchStudy/research-study.svg +9 -0
- package/src/assets/containers/ResourceCategory/resource-placeholder.svg +3 -0
- package/src/components/containers/Accordion/Accordion.js +80 -0
- package/src/components/containers/Accordion/Accordion.stories.js +76 -0
- package/src/components/containers/Accordion/index.js +3 -0
- package/src/components/containers/ResourceContainer/ResourceContainer.css +0 -1
- package/src/components/containers/ResourceContainer/ResourceContainer.js +1 -1
- package/src/components/datatypes/AccountBalance/AccountBalance.js +33 -0
- package/src/components/datatypes/AccountBalance/index.js +3 -0
- package/src/components/datatypes/Annotation/Annotation.js +1 -1
- package/src/components/datatypes/Date/Date.js +14 -4
- package/src/components/datatypes/DatePeriod/DatePeriod.js +38 -0
- package/src/components/datatypes/DatePeriod/index.js +3 -0
- package/src/components/datatypes/HeaderIcon/HeaderIcon.js +31 -0
- package/src/components/datatypes/HeaderIcon/index.js +3 -0
- package/src/components/datatypes/HumanName/HumanName.js +6 -21
- package/src/components/datatypes/Reference/Reference.js +3 -6
- package/src/components/resources/AdverseEvent/AdverseEvent.test.js +2 -2
- package/src/components/resources/AllergyIntolerance/AllergyIntolerance.test.js +4 -4
- package/src/components/resources/Appointment/Appointment.js +91 -65
- package/src/components/resources/Appointment/Appointment.test.js +3 -3
- package/src/components/resources/Bundle/Bundle.js +2 -2
- package/src/components/resources/Bundle/Bundle.stories.js +78 -12
- package/src/components/resources/Bundle/Bundle.test.js +3 -0
- package/src/components/resources/CarePlan/CarePlan.test.js +4 -4
- package/src/components/resources/CareTeam/CareTeam.js +13 -14
- package/src/components/resources/CareTeam/CareTeam.test.js +4 -4
- package/src/components/resources/Claim/Claim.test.js +6 -6
- package/src/components/resources/ClaimResponse/ClaimResponse.test.js +6 -6
- package/src/components/resources/Condition/Condition.js +63 -47
- package/src/components/resources/Condition/Condition.stories.js +41 -8
- package/src/components/resources/Condition/Condition.test.js +20 -14
- package/src/components/resources/DiagnosticReport/DiagnosticReport.test.js +5 -7
- package/src/components/resources/DocumentReference/DocumentReference.js +1 -1
- package/src/components/resources/DocumentReference/DocumentReference.test.js +3 -3
- package/src/components/resources/Encounter/Encounter.js +66 -36
- package/src/components/resources/Encounter/EncounterParticipants.js +2 -2
- package/src/components/resources/ExplanationOfBenefit/CareTeam.js +2 -2
- package/src/components/resources/ExplanationOfBenefit/Diagnosis.js +15 -5
- package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.js +272 -201
- package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.test.js +79 -62
- package/src/components/resources/ExplanationOfBenefit/Items.js +2 -2
- package/src/components/resources/ExplanationOfBenefit/PriceLabel.js +20 -0
- package/src/components/resources/ExplanationOfBenefit/Related.js +3 -3
- package/src/components/resources/ExplanationOfBenefit/SupportingInfo.js +14 -3
- package/src/components/resources/ExplanationOfBenefit/TotalGraph.js +68 -0
- package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.js +89 -0
- package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.stories.js +78 -0
- package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.test.js +51 -0
- package/src/components/resources/ExplanationOfBenefitGraph/index.js +3 -0
- package/src/components/resources/Goal/Goal.test.js +1 -1
- package/src/components/resources/Immunization/Immunization.js +125 -94
- package/src/components/resources/Immunization/Immunization.stories.js +23 -4
- package/src/components/resources/Immunization/Immunization.test.js +17 -12
- package/src/components/resources/List/List.test.js +3 -3
- package/src/components/resources/MedicationAdministration/MedicationAdministration.test.js +7 -7
- package/src/components/resources/MedicationDispense/MedicationDispense.test.js +2 -2
- package/src/components/resources/MedicationRequest/MedicationRequest.test.js +4 -4
- package/src/components/resources/Observation/Observation.js +72 -54
- package/src/components/resources/Observation/Observation.test.js +6 -18
- package/src/components/resources/Observation/ObservationGraph.js +159 -55
- package/src/components/resources/Observation/ObservationGraph.test.js +47 -26
- package/src/components/resources/Patient/Patient.js +77 -87
- package/src/components/resources/Patient/Patient.test.js +1 -1
- package/src/components/resources/Practitioner/Practitioner.js +80 -60
- package/src/components/resources/Practitioner/Practitioner.test.js +4 -4
- package/src/components/resources/Procedure/Procedure.js +99 -87
- package/src/components/resources/Procedure/Procedure.stories.js +8 -6
- package/src/components/resources/Procedure/Procedure.test.js +11 -8
- package/src/components/resources/Questionnaire/Questionnaire.test.js +3 -3
- package/src/components/resources/QuestionnaireResponse/QuestionnaireResponse.test.js +5 -5
- package/src/components/resources/ReferralRequest/ReferralRequest.test.js +2 -2
- package/src/components/resources/ResearchStudy/ResearchStudy.test.js +1 -1
- package/src/components/resources/ResourceCategory/ResourceCategory.js +56 -0
- package/src/components/resources/ResourceCategory/ResourceCategory.stories.js +29 -0
- package/src/components/resources/ResourceCategory/ResourceCategory.test.js +101 -0
- package/src/components/resources/ResourceCategory/index.js +3 -0
- package/src/components/ui/_header.scss +3 -0
- package/src/components/ui/bootstrap-reboot.min.css +2 -22
- package/src/components/ui/index.js +191 -29
- package/src/constants/badge-status.jsx +98 -0
- package/src/fixtures/dstu2/resources/condition/condition.svg +35 -0
- package/src/fixtures/dstu2/resources/immunization/immunization.svg +10 -0
- package/src/fixtures/example-icons.jsx +169 -0
- package/src/index.js +6 -1
- package/src/style.scss +176 -0
- package/src/utils/formatDate.js +21 -0
- package/src/utils/formatDate.test.js +22 -0
- package/src/utils/getBadgeColor.js +6 -0
- package/src/utils/getBadgeColor.test.js +14 -0
- package/src/utils/isUrl.js +9 -0
- package/src/utils/isUrl.test.js +12 -0
- package/src/utils.js +7 -0
- package/build/bootstrap-reboot.min.css +0 -414
- package/build/index.js +0 -15
- package/build/style.css +0 -459
- package/src/components/datatypes/HumanName/HumanName.css +0 -15
- package/src/components/datatypes/Reference/Reference.css +0 -8
- package/src/components/resources/Observation/ObservationGraph.css +0 -51
- package/src/components/resources/Patient/Patient.css +0 -19
- package/src/components/ui/index.css +0 -123
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
3
|
-
|
|
4
1
|
import ExplanationOfBenefit from './ExplanationOfBenefit';
|
|
5
|
-
import
|
|
6
|
-
import fhirVersions from '../fhirResourceVersions';
|
|
2
|
+
import React from 'react';
|
|
7
3
|
import dstu2Example1 from '../../../fixtures/dstu2/resources/explanationOfBenefit/example1.json';
|
|
4
|
+
import example1R4 from '../../../fixtures/r4/resources/explanationOfBenefit/personPrimaryCoverage.json';
|
|
8
5
|
import example1Stu3 from '../../../fixtures/stu3/resources/explanationOfBenefit/example1.json';
|
|
9
6
|
import example2Stu3 from '../../../fixtures/stu3/resources/explanationOfBenefit/example2.json';
|
|
10
|
-
import example1R4 from '../../../fixtures/r4/resources/explanationOfBenefit/personPrimaryCoverage.json';
|
|
11
7
|
import exampleC4BB from '../../../fixtures/r4/resources/explanationOfBenefit/c4bbExample.json';
|
|
12
8
|
import exampleC4BBExtendedDiagnosis from '../../../fixtures/r4/resources/explanationOfBenefit/c4bbExtendedDiagnosis.json';
|
|
9
|
+
import fhirVersions from '../fhirResourceVersions';
|
|
10
|
+
import { nbspRegex } from '../../../testUtils';
|
|
11
|
+
import { render } from '@testing-library/react';
|
|
13
12
|
|
|
14
13
|
describe('should render ExplanationOfBenefit component properly', () => {
|
|
15
14
|
it('should render with DSTU2 source data', () => {
|
|
@@ -24,7 +23,7 @@ describe('should render ExplanationOfBenefit component properly', () => {
|
|
|
24
23
|
expect(container).not.toBeNull();
|
|
25
24
|
|
|
26
25
|
expect(getByTestId('title').textContent).toContain('Claim settled as ');
|
|
27
|
-
expect(getByTestId('created').textContent).toContain('2014
|
|
26
|
+
expect(getByTestId('created').textContent).toContain('8/16/2014');
|
|
28
27
|
expect(getByTestId('insurer').textContent).toContain('Organization/2');
|
|
29
28
|
});
|
|
30
29
|
|
|
@@ -40,13 +39,11 @@ describe('should render ExplanationOfBenefit component properly', () => {
|
|
|
40
39
|
expect(container).not.toBeNull();
|
|
41
40
|
|
|
42
41
|
expect(getByTestId('title').textContent).toContain('Claim settled as ');
|
|
43
|
-
expect(getByTestId('created').textContent).toContain('2014
|
|
42
|
+
expect(getByTestId('created').textContent).toContain('8/16/2014');
|
|
43
|
+
expect(getByTestId('metricAmount').textContent).toContain('$135.57');
|
|
44
44
|
expect(
|
|
45
|
-
getByTestId('
|
|
46
|
-
).
|
|
47
|
-
expect(
|
|
48
|
-
getByTestId('totalBenefit').textContent.replace(nbspRegex, ' '),
|
|
49
|
-
).toContain('96 USD');
|
|
45
|
+
getByTestId('planDiscount').textContent.replace(nbspRegex, ' '),
|
|
46
|
+
).toContain('$96.00');
|
|
50
47
|
expect(getByTestId('hasServices').textContent).toContain('(1200)');
|
|
51
48
|
});
|
|
52
49
|
|
|
@@ -72,15 +69,19 @@ describe('should render ExplanationOfBenefit component properly', () => {
|
|
|
72
69
|
fhirVersion: fhirVersions.R4,
|
|
73
70
|
};
|
|
74
71
|
|
|
75
|
-
const {
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
const {
|
|
73
|
+
container,
|
|
74
|
+
getByTestId,
|
|
75
|
+
queryByTestId,
|
|
76
|
+
getAllByTestId,
|
|
77
|
+
getAllByRole,
|
|
78
|
+
} = render(<ExplanationOfBenefit {...defaultProps} />);
|
|
78
79
|
expect(container).not.toBeNull();
|
|
79
80
|
|
|
80
81
|
expect(getByTestId('title').textContent).toEqual(
|
|
81
82
|
'Claim settled as per contract.',
|
|
82
83
|
);
|
|
83
|
-
expect(getByTestId('created').textContent).toEqual('2014
|
|
84
|
+
expect(getByTestId('created').textContent).toEqual('8/16/2014');
|
|
84
85
|
expect(getByTestId('insurer').textContent).toEqual('Organization/3');
|
|
85
86
|
expect(getByTestId('provider').textContent).toEqual('Practitioner/1');
|
|
86
87
|
expect(getByTestId('totalSum').textContent).toContain('135.57');
|
|
@@ -89,38 +90,43 @@ describe('should render ExplanationOfBenefit component properly', () => {
|
|
|
89
90
|
expect(getByTestId('insurance').textContent).toEqual('Coverage/9876B1');
|
|
90
91
|
|
|
91
92
|
expect(queryByTestId('hasServices')).not.toBeNull();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
tds.push(String(item.textContent).trim());
|
|
99
|
-
});
|
|
100
|
-
tablesContent.push(tds);
|
|
101
|
-
});
|
|
102
|
-
// table header
|
|
103
|
-
expect(tablesContent[0]).toEqual([
|
|
93
|
+
|
|
94
|
+
// checking if text content of each header cell is equal to mocked data
|
|
95
|
+
const headerCells = getAllByRole('columnheader')
|
|
96
|
+
.slice(0, 4)
|
|
97
|
+
.map(x => x.textContent);
|
|
98
|
+
expect(headerCells).toEqual([
|
|
104
99
|
'Service',
|
|
105
100
|
'Service date',
|
|
106
101
|
'Quantity',
|
|
107
102
|
'Item cost',
|
|
108
103
|
]);
|
|
109
104
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
// checking if text content of each column is equal to mocked data
|
|
106
|
+
const explanationService = getAllByTestId('explanation.service').map(
|
|
107
|
+
n => n.textContent,
|
|
108
|
+
);
|
|
109
|
+
const expectedArray = ['(1205)', '(group)'];
|
|
110
|
+
explanationService.forEach((x, i) => expect(x).toContain(expectedArray[i]));
|
|
111
|
+
|
|
112
|
+
const explanationServicedDate = getAllByTestId(
|
|
113
|
+
'explanation.servicedDate',
|
|
114
|
+
).map(n => n.textContent);
|
|
115
|
+
expect(explanationServicedDate).toEqual(['8/16/2014', '8/16/2014']);
|
|
116
|
+
|
|
117
|
+
const explanationQuantity = getAllByTestId('explanation.quantity').map(
|
|
118
|
+
n => n.textContent,
|
|
119
|
+
);
|
|
120
|
+
expect(explanationQuantity).toEqual(['-', '-']);
|
|
121
|
+
|
|
122
|
+
const explanationItemCost = getAllByTestId('explanation.itemCost').map(
|
|
123
|
+
n => n.textContent,
|
|
124
|
+
);
|
|
125
|
+
expect(explanationItemCost).toEqual([
|
|
115
126
|
`135.57${String.fromCharCode(160)}USD`,
|
|
116
|
-
]);
|
|
117
|
-
// table 2nd row
|
|
118
|
-
expect(tablesContent[2]).toEqual([
|
|
119
|
-
'(group)',
|
|
120
|
-
'2014-08-16',
|
|
121
|
-
'-',
|
|
122
127
|
`200${String.fromCharCode(160)}USD`,
|
|
123
128
|
]);
|
|
129
|
+
|
|
124
130
|
expect(queryByTestId('hasInformation')).toBeNull();
|
|
125
131
|
expect(queryByTestId('totalBenefit')).toBeNull();
|
|
126
132
|
expect(queryByTestId('totalCost')).toBeNull();
|
|
@@ -156,12 +162,17 @@ describe('should render ExplanationOfBenefit component properly', () => {
|
|
|
156
162
|
withCarinBBProfile: true,
|
|
157
163
|
};
|
|
158
164
|
|
|
159
|
-
const {
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
const {
|
|
166
|
+
container,
|
|
167
|
+
getByTestId,
|
|
168
|
+
queryByTestId,
|
|
169
|
+
queryAllByTestId,
|
|
170
|
+
getAllByTestId,
|
|
171
|
+
getAllByRole,
|
|
172
|
+
} = render(<ExplanationOfBenefit {...defaultProps} />);
|
|
162
173
|
expect(container).not.toBeNull();
|
|
163
174
|
|
|
164
|
-
expect(getByTestId('created').textContent).toEqual('2017
|
|
175
|
+
expect(getByTestId('created').textContent).toEqual('1/5/2017');
|
|
165
176
|
expect(getByTestId('identifier').textContent).toContain(
|
|
166
177
|
'c145d3fe-d56e-dc26-75e9-01e90672f506',
|
|
167
178
|
);
|
|
@@ -176,7 +187,7 @@ describe('should render ExplanationOfBenefit component properly', () => {
|
|
|
176
187
|
'Organization/iAxXvHiphwGGAL48m3B7XXtKlLZg6yXnC1ch84x1up',
|
|
177
188
|
);
|
|
178
189
|
expect(getByTestId('billablePeriod').textContent).toEqual(
|
|
179
|
-
'From: 2017
|
|
190
|
+
'From: 1/5/2017; To: 1/5/2018',
|
|
180
191
|
);
|
|
181
192
|
expect(getByTestId('patient').textContent).toEqual(
|
|
182
193
|
'Patient/f56391c2-dd54-b378-46ef-87c1643a2ba0',
|
|
@@ -193,29 +204,35 @@ describe('should render ExplanationOfBenefit component properly', () => {
|
|
|
193
204
|
'clmrecvddate',
|
|
194
205
|
);
|
|
195
206
|
expect(getByTestId('supportingInfo.timingDate').textContent).toEqual(
|
|
196
|
-
'2017
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
.
|
|
202
|
-
.
|
|
203
|
-
|
|
204
|
-
el.querySelectorAll('.fhir-ui__TableCell').forEach(item => {
|
|
205
|
-
tds.push(String(item.textContent).trim());
|
|
206
|
-
});
|
|
207
|
-
tablesContent.push(tds);
|
|
208
|
-
});
|
|
209
|
-
// table header
|
|
210
|
-
expect(tablesContent[0]).toEqual([
|
|
207
|
+
'1/5/2017',
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// checking if text content of each header cell is equal to mocked data
|
|
211
|
+
const headerCells = getAllByRole('columnheader')
|
|
212
|
+
.slice(0, 4)
|
|
213
|
+
.map(x => x.textContent);
|
|
214
|
+
expect(headerCells).toEqual([
|
|
211
215
|
'Service',
|
|
212
216
|
'Service date',
|
|
213
217
|
'Quantity',
|
|
214
218
|
'Item cost',
|
|
215
219
|
]);
|
|
216
220
|
|
|
217
|
-
//
|
|
218
|
-
|
|
221
|
+
// checking if text content of first column is equal to mocked data
|
|
222
|
+
const explanationService = getAllByTestId('explanation.service').map(
|
|
223
|
+
n => n.textContent,
|
|
224
|
+
);
|
|
225
|
+
const expectedArray = [
|
|
226
|
+
'Encounter for symptom (185345009)',
|
|
227
|
+
'Acute bronchitis (disorder) (10509002)',
|
|
228
|
+
'Measurement of respiratory function (procedure) (23426006)',
|
|
229
|
+
];
|
|
230
|
+
const replaceWhitespaces = text => text.replace(/\s+/g, ' ');
|
|
231
|
+
explanationService.forEach((x, i) => {
|
|
232
|
+
expect(replaceWhitespaces(x)).toEqual(
|
|
233
|
+
replaceWhitespaces(expectedArray[i]),
|
|
234
|
+
);
|
|
235
|
+
});
|
|
219
236
|
|
|
220
237
|
expect(queryAllByTestId('items.level')).not.toBeNull();
|
|
221
238
|
expect(queryAllByTestId('items.sequence')).not.toBeNull();
|
|
@@ -18,7 +18,7 @@ import Period from '../../datatypes/Period';
|
|
|
18
18
|
const Items = ({ fhirData: items = [] }) => {
|
|
19
19
|
if (items.length === 0) return null;
|
|
20
20
|
return (
|
|
21
|
-
<ValueSection label="Items" data-testid="items">
|
|
21
|
+
<ValueSection label="Items" data-testid="items" marginTop>
|
|
22
22
|
<Table>
|
|
23
23
|
<thead>
|
|
24
24
|
<TableRow>
|
|
@@ -33,7 +33,7 @@ const Items = ({ fhirData: items = [] }) => {
|
|
|
33
33
|
<TableHeader>Net</TableHeader>
|
|
34
34
|
</TableRow>
|
|
35
35
|
</thead>
|
|
36
|
-
<tbody>
|
|
36
|
+
<tbody className="border-top-0">
|
|
37
37
|
{items.map((item, idx) => (
|
|
38
38
|
<Item key={idx} item={item} level={0} parentSequences={[]} />
|
|
39
39
|
))}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { parseValueIntoMonetaryValueOfGivenCurrency } from '../../../utils';
|
|
3
|
+
|
|
4
|
+
const PriceLabel = ({ ...props }) => {
|
|
5
|
+
const { totalCost } = props;
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<h3
|
|
9
|
+
className="fw-bold fs-2 mb-0 w-90 title-width-sm"
|
|
10
|
+
data-testid="headerPrice"
|
|
11
|
+
>
|
|
12
|
+
{parseValueIntoMonetaryValueOfGivenCurrency(
|
|
13
|
+
totalCost.value,
|
|
14
|
+
totalCost.code,
|
|
15
|
+
)}
|
|
16
|
+
</h3>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default PriceLabel;
|
|
@@ -15,17 +15,17 @@ const Related = ({ fhirData }) => {
|
|
|
15
15
|
return (
|
|
16
16
|
<div key={`total-${index}`}>
|
|
17
17
|
{claim && (
|
|
18
|
-
<Value label="Claim" data-testid="claim">
|
|
18
|
+
<Value label="Claim" data-testid="claim" dirColumn>
|
|
19
19
|
<Reference fhirData={claim} />
|
|
20
20
|
</Value>
|
|
21
21
|
)}
|
|
22
22
|
{relationship && (
|
|
23
|
-
<Value label="Relationship" data-testid="relationship">
|
|
23
|
+
<Value label="Relationship" data-testid="relationship" dirColumn>
|
|
24
24
|
<CodeableConcept fhirData={relationship} />
|
|
25
25
|
</Value>
|
|
26
26
|
)}
|
|
27
27
|
{reference && (
|
|
28
|
-
<Value label="Reference" data-testid="reference">
|
|
28
|
+
<Value label="Reference" data-testid="reference" dirColumn>
|
|
29
29
|
<Identifier fhirData={reference} />
|
|
30
30
|
</Value>
|
|
31
31
|
)}
|
|
@@ -19,24 +19,34 @@ const SupportingInfo = ({ fhirData }) => {
|
|
|
19
19
|
<ValueSection
|
|
20
20
|
label={`Supporting information ${sequence}.`}
|
|
21
21
|
data-testid="supportingInfo"
|
|
22
|
+
marginTop
|
|
22
23
|
>
|
|
23
24
|
{category && (
|
|
24
|
-
<Value
|
|
25
|
+
<Value
|
|
26
|
+
dirColumn
|
|
27
|
+
label="Category"
|
|
28
|
+
data-testid="supportingInfo.category"
|
|
29
|
+
>
|
|
25
30
|
<CodeableConcept fhirData={category} />
|
|
26
31
|
</Value>
|
|
27
32
|
)}
|
|
28
33
|
{code && (
|
|
29
|
-
<Value label="Code" data-testid="supportingInfo.code">
|
|
34
|
+
<Value dirColumn label="Code" data-testid="supportingInfo.code">
|
|
30
35
|
<CodeableConcept fhirData={code} />
|
|
31
36
|
</Value>
|
|
32
37
|
)}
|
|
33
38
|
{timingDate && (
|
|
34
|
-
<Value
|
|
39
|
+
<Value
|
|
40
|
+
dirColumn
|
|
41
|
+
label="Date"
|
|
42
|
+
data-testid="supportingInfo.timingDate"
|
|
43
|
+
>
|
|
35
44
|
<Date fhirData={timingDate} />
|
|
36
45
|
</Value>
|
|
37
46
|
)}
|
|
38
47
|
{timingPeriodStart && (
|
|
39
48
|
<Value
|
|
49
|
+
dirColumn
|
|
40
50
|
label="Start date"
|
|
41
51
|
data-testid="supportingInfo.timingPeriodStart"
|
|
42
52
|
>
|
|
@@ -45,6 +55,7 @@ const SupportingInfo = ({ fhirData }) => {
|
|
|
45
55
|
)}
|
|
46
56
|
{timingPeriodEnd && (
|
|
47
57
|
<Value
|
|
58
|
+
dirColumn
|
|
48
59
|
label="End date"
|
|
49
60
|
data-testid="supportingInfo.timingPeriodEnd"
|
|
50
61
|
>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Value } from '../../ui/index';
|
|
4
|
+
import { ValueSection } from '../../ui/index';
|
|
5
|
+
import ExplanationOfBenefitGraph from '../ExplanationOfBenefitGraph/ExplanationOfBenefitGraph';
|
|
6
|
+
import { parseValueIntoMonetaryValueOfGivenCurrency } from '../../../utils';
|
|
7
|
+
|
|
8
|
+
const TotalGraph = ({ fhirData }) => {
|
|
9
|
+
const { totalCost, totalBenefit } = fhirData;
|
|
10
|
+
|
|
11
|
+
// currently supported format: STU3
|
|
12
|
+
const getChartData = () => {
|
|
13
|
+
if (totalCost && totalBenefit) {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
id: 'youPaid',
|
|
17
|
+
label: 'You paid',
|
|
18
|
+
value: totalCost.value - totalBenefit.value,
|
|
19
|
+
color: '#0D6EFD',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'planDiscount',
|
|
23
|
+
label: 'Plan discount',
|
|
24
|
+
value: totalBenefit.value,
|
|
25
|
+
color: '#FFC107',
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<ValueSection label="Total" data-testid="total" marginTop>
|
|
33
|
+
<div className="bg-light my-3 py-2 d-flex flex-column flex-sm-row">
|
|
34
|
+
<div className="graph-width-sm">
|
|
35
|
+
<ExplanationOfBenefitGraph
|
|
36
|
+
pieChartProperties={{ isInteractive: false }}
|
|
37
|
+
data={getChartData({ totalCost, totalBenefit })}
|
|
38
|
+
margin={{ top: 20, bottom: 20 }}
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="my-sm-auto">
|
|
42
|
+
<div className="row justify-content-center">
|
|
43
|
+
{getChartData({ totalCost, totalBenefit }).map((item, index) => (
|
|
44
|
+
<div
|
|
45
|
+
key={`graph-legend-item-${index}`}
|
|
46
|
+
style={{ minWidth: 160 }}
|
|
47
|
+
className="d-flex mb-2 px-3 w-auto"
|
|
48
|
+
>
|
|
49
|
+
<span
|
|
50
|
+
className="me-2 rounded-pill mt-3 mb-2 my-sm-0"
|
|
51
|
+
style={{ width: 4, background: item.color }}
|
|
52
|
+
/>
|
|
53
|
+
<Value dirColumn label={item.label} data-testid={item.id}>
|
|
54
|
+
{parseValueIntoMonetaryValueOfGivenCurrency(
|
|
55
|
+
item.value,
|
|
56
|
+
totalBenefit.code,
|
|
57
|
+
)}
|
|
58
|
+
</Value>
|
|
59
|
+
</div>
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</ValueSection>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default TotalGraph;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Root } from '../../ui';
|
|
4
|
+
import { ResponsivePie } from '@nivo/pie';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
|
|
7
|
+
const ExplanationOfBenefitGraph = props => {
|
|
8
|
+
const {
|
|
9
|
+
data,
|
|
10
|
+
totalLabel,
|
|
11
|
+
height,
|
|
12
|
+
margin,
|
|
13
|
+
enableValueLabels,
|
|
14
|
+
enableLinkLabels,
|
|
15
|
+
pieChartProperties,
|
|
16
|
+
} = props;
|
|
17
|
+
|
|
18
|
+
const getTotalAmount = () => {
|
|
19
|
+
const total = data.reduce((n, { value }) => n + value, 0);
|
|
20
|
+
return `$${Number(total).toFixed(2)}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getValidMargin = margin => {
|
|
24
|
+
const resultMargin = {};
|
|
25
|
+
if (margin) {
|
|
26
|
+
if ('top' in margin) resultMargin.top = margin.top;
|
|
27
|
+
if ('right' in margin) resultMargin.right = margin.right;
|
|
28
|
+
if ('bottom' in margin) resultMargin.bottom = margin.bottom;
|
|
29
|
+
if ('left' in margin) resultMargin.left = margin.left;
|
|
30
|
+
}
|
|
31
|
+
return resultMargin;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const CenteredMetric = () => (
|
|
35
|
+
<div className="position-absolute d-flex flex-column w-100 translate-middle-y top-50">
|
|
36
|
+
<h6 className="text-secondary" data-testid="metricText">
|
|
37
|
+
{totalLabel || 'Total'}
|
|
38
|
+
</h6>
|
|
39
|
+
<h5 className="fw-bold text-dark" data-testid="metricAmount">
|
|
40
|
+
{getTotalAmount()}
|
|
41
|
+
</h5>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Root name="ExplanationOfBenefitGraph">
|
|
47
|
+
{/* according to nivo library documentation, to keep Pie Chart svg aligned, 'height' prop has to be constant */}
|
|
48
|
+
<div
|
|
49
|
+
style={{ height: height || 200 }}
|
|
50
|
+
className="position-relative text-center"
|
|
51
|
+
data-testid="responsivePie"
|
|
52
|
+
>
|
|
53
|
+
<ResponsivePie
|
|
54
|
+
data={data}
|
|
55
|
+
margin={getValidMargin(margin)}
|
|
56
|
+
colors={{ datum: 'data.color' }}
|
|
57
|
+
enableArcLabels={enableValueLabels || false}
|
|
58
|
+
enableArcLinkLabels={enableLinkLabels || false}
|
|
59
|
+
innerRadius={0.88}
|
|
60
|
+
activeOuterRadiusOffset={1}
|
|
61
|
+
borderWidth={0.1}
|
|
62
|
+
borderColor={{ from: 'color', modifiers: [['darker', 0.2]] }}
|
|
63
|
+
{...pieChartProperties}
|
|
64
|
+
/>
|
|
65
|
+
<CenteredMetric />
|
|
66
|
+
</div>
|
|
67
|
+
</Root>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
ExplanationOfBenefitGraph.propTypes = {
|
|
72
|
+
data: PropTypes.arrayOf(
|
|
73
|
+
PropTypes.shape({
|
|
74
|
+
id: PropTypes.string.isRequired,
|
|
75
|
+
label: PropTypes.string.isRequired,
|
|
76
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
77
|
+
.isRequired,
|
|
78
|
+
color: PropTypes.string.isRequired,
|
|
79
|
+
}),
|
|
80
|
+
).isRequired,
|
|
81
|
+
height: PropTypes.string,
|
|
82
|
+
margin: PropTypes.shape({}),
|
|
83
|
+
enableValueLabels: PropTypes.bool,
|
|
84
|
+
enableLinkLabels: PropTypes.bool,
|
|
85
|
+
totalLabel: PropTypes.string,
|
|
86
|
+
pieChartProperties: PropTypes.shape({}),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default ExplanationOfBenefitGraph;
|
package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.stories.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import ExplanationOfBenefitGraph from './ExplanationOfBenefitGraph';
|
|
4
|
+
|
|
5
|
+
export default { title: 'ExplanationOfBenefitGraph' };
|
|
6
|
+
|
|
7
|
+
const CHART_DATA = [
|
|
8
|
+
{
|
|
9
|
+
id: 'a',
|
|
10
|
+
label: 'a',
|
|
11
|
+
value: 35,
|
|
12
|
+
color: '#3498DB',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'b',
|
|
16
|
+
label: 'b',
|
|
17
|
+
value: 200,
|
|
18
|
+
color: '#17A589',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'c',
|
|
22
|
+
label: 'c',
|
|
23
|
+
value: 76,
|
|
24
|
+
color: '#D4AC0D',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'd',
|
|
28
|
+
label: 'd',
|
|
29
|
+
value: 76,
|
|
30
|
+
color: '#EDBB99',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const DefaultExplanationOfBenefitGraph = () => {
|
|
35
|
+
return (
|
|
36
|
+
<ExplanationOfBenefitGraph
|
|
37
|
+
data={CHART_DATA}
|
|
38
|
+
margin={{ top: 10, bottom: 10 }}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const ExplanationOfBenefitGraphWithCustomCenteredMetric = () => {
|
|
44
|
+
return <ExplanationOfBenefitGraph data={CHART_DATA} totalLabel="Custom" />;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const ExplanationOfBenefitGraphWithHeightAndMargin = () => {
|
|
48
|
+
return (
|
|
49
|
+
<ExplanationOfBenefitGraph
|
|
50
|
+
data={CHART_DATA}
|
|
51
|
+
margin={{ top: 40, bottom: 40 }}
|
|
52
|
+
height={250}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const ExplanationOfBenefitGraphWithLabels = () => {
|
|
58
|
+
return (
|
|
59
|
+
<ExplanationOfBenefitGraph
|
|
60
|
+
data={CHART_DATA}
|
|
61
|
+
margin={{ top: 20, bottom: 20 }}
|
|
62
|
+
enableLinkLabels
|
|
63
|
+
enableValueLabels
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const ExplanationOfBenefitGraphWithPieChartProperties = () => {
|
|
69
|
+
return (
|
|
70
|
+
<ExplanationOfBenefitGraph
|
|
71
|
+
data={CHART_DATA}
|
|
72
|
+
margin={{ top: 20, bottom: 20 }}
|
|
73
|
+
enableLinkLabels
|
|
74
|
+
enableValueLabels
|
|
75
|
+
pieChartProperties={{ startAngle: 90, cornerRadius: 15, borderWidth: 4 }}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import ExplanationOfBenefitGraph from './ExplanationOfBenefitGraph';
|
|
4
|
+
|
|
5
|
+
const CHART_DATA = [
|
|
6
|
+
{
|
|
7
|
+
id: 'a',
|
|
8
|
+
label: 'a',
|
|
9
|
+
value: 35,
|
|
10
|
+
color: '#3498DB',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: 'b',
|
|
14
|
+
label: 'b',
|
|
15
|
+
value: 200,
|
|
16
|
+
color: '#17A589',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'c',
|
|
20
|
+
label: 'c',
|
|
21
|
+
value: 76,
|
|
22
|
+
color: '#D4AC0D',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'd',
|
|
26
|
+
label: 'd',
|
|
27
|
+
value: 76,
|
|
28
|
+
color: '#EDBB99',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
describe('should render ExplanationOfBenefitGraph properly', () => {
|
|
33
|
+
it('should render with ExplanationOfBenefitGraph data', () => {
|
|
34
|
+
const defaultProps = {
|
|
35
|
+
data: CHART_DATA,
|
|
36
|
+
totalLabel: 'Custom',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const { container, getByTestId } = render(
|
|
40
|
+
<ExplanationOfBenefitGraph {...defaultProps} />,
|
|
41
|
+
);
|
|
42
|
+
expect(container).not.toBeNull();
|
|
43
|
+
|
|
44
|
+
expect(getByTestId('responsivePie')).not.toBeNull();
|
|
45
|
+
expect(getByTestId('metricText').textContent).toContain('Custom');
|
|
46
|
+
const totalValue = CHART_DATA.reduce((n, { value }) => n + value, 0);
|
|
47
|
+
expect(getByTestId('metricAmount').textContent).toContain(
|
|
48
|
+
`$${Number(totalValue).toFixed(2)}`,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -70,7 +70,7 @@ describe('should render Goal component properly', () => {
|
|
|
70
70
|
'Peter James Chalmers',
|
|
71
71
|
);
|
|
72
72
|
|
|
73
|
-
expect(getByTestId('statusDate').textContent).toEqual('2016
|
|
73
|
+
expect(getByTestId('statusDate').textContent).toEqual('2/14/2016');
|
|
74
74
|
|
|
75
75
|
expect(getByTestId('description').textContent).toEqual(
|
|
76
76
|
'Target weight is 160 to 180 lbs.',
|