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.
Files changed (135) hide show
  1. package/.github/workflows/publish_npmjs.yml +18 -0
  2. package/.storybook/config.js +9 -3
  3. package/.storybook/presets.js +1 -0
  4. package/.storybook/preview-head.html +4 -0
  5. package/README.md +47 -3
  6. package/package.json +13 -4
  7. package/src/assets/common/chevron-right.svg +3 -0
  8. package/src/assets/containers/AllergyIntolerance/allergy-intolerance.svg +9 -0
  9. package/src/assets/containers/Appointment/appointment.svg +14 -0
  10. package/src/assets/containers/CarePlan/care-plan.svg +10 -0
  11. package/src/assets/containers/CareTeam/care-team.svg +10 -0
  12. package/src/assets/containers/Claim/claim.svg +6 -0
  13. package/src/assets/containers/ClaimResponse/claim-response.svg +7 -0
  14. package/src/assets/containers/Condition/condition.svg +11 -0
  15. package/src/assets/containers/Device/device.svg +8 -0
  16. package/src/assets/containers/DiagnosticReport/diagnostic-report.svg +14 -0
  17. package/src/assets/containers/DocumentReference/document-reference.svg +10 -0
  18. package/src/assets/containers/Encounter/encounter.svg +10 -0
  19. package/src/assets/containers/ExplanationOfBenefit/explanation-of-benefit.svg +3 -0
  20. package/src/assets/containers/FamilyMemberHistory/family-member-history.svg +7 -0
  21. package/src/assets/containers/Goal/goal.svg +11 -0
  22. package/src/assets/containers/Immunization/immunization.svg +7 -0
  23. package/src/assets/containers/List/list.svg +3 -0
  24. package/src/assets/containers/Location/location.svg +4 -0
  25. package/src/assets/containers/Medication/medication.svg +5 -0
  26. package/src/assets/containers/MedicationAdministration/medication-administration.svg +6 -0
  27. package/src/assets/containers/MedicationKnowledge/medication-knowledge.svg +11 -0
  28. package/src/assets/containers/MedicationStatement/medication-statement.svg +5 -0
  29. package/src/assets/containers/Observation/observation.svg +12 -0
  30. package/src/assets/containers/Practitioner/practitioner.svg +5 -0
  31. package/src/assets/containers/Procedure/procedure.svg +9 -0
  32. package/src/assets/containers/Questionnaire/questionnaire.svg +6 -0
  33. package/src/assets/containers/QuestionnaireResponse/questionnaire-response.svg +6 -0
  34. package/src/assets/containers/QustionnaireResponse/questionnaire-response.svg +6 -0
  35. package/src/assets/containers/ResearchStudy/research-study.svg +9 -0
  36. package/src/assets/containers/ResourceCategory/resource-placeholder.svg +3 -0
  37. package/src/components/containers/Accordion/Accordion.js +80 -0
  38. package/src/components/containers/Accordion/Accordion.stories.js +76 -0
  39. package/src/components/containers/Accordion/index.js +3 -0
  40. package/src/components/containers/ResourceContainer/ResourceContainer.css +0 -1
  41. package/src/components/containers/ResourceContainer/ResourceContainer.js +1 -1
  42. package/src/components/datatypes/AccountBalance/AccountBalance.js +33 -0
  43. package/src/components/datatypes/AccountBalance/index.js +3 -0
  44. package/src/components/datatypes/Annotation/Annotation.js +1 -1
  45. package/src/components/datatypes/Date/Date.js +14 -4
  46. package/src/components/datatypes/DatePeriod/DatePeriod.js +38 -0
  47. package/src/components/datatypes/DatePeriod/index.js +3 -0
  48. package/src/components/datatypes/HeaderIcon/HeaderIcon.js +31 -0
  49. package/src/components/datatypes/HeaderIcon/index.js +3 -0
  50. package/src/components/datatypes/HumanName/HumanName.js +6 -21
  51. package/src/components/datatypes/Reference/Reference.js +3 -6
  52. package/src/components/resources/AdverseEvent/AdverseEvent.test.js +2 -2
  53. package/src/components/resources/AllergyIntolerance/AllergyIntolerance.test.js +4 -4
  54. package/src/components/resources/Appointment/Appointment.js +91 -65
  55. package/src/components/resources/Appointment/Appointment.test.js +3 -3
  56. package/src/components/resources/Bundle/Bundle.js +2 -2
  57. package/src/components/resources/Bundle/Bundle.stories.js +78 -12
  58. package/src/components/resources/Bundle/Bundle.test.js +3 -0
  59. package/src/components/resources/CarePlan/CarePlan.test.js +4 -4
  60. package/src/components/resources/CareTeam/CareTeam.js +13 -14
  61. package/src/components/resources/CareTeam/CareTeam.test.js +4 -4
  62. package/src/components/resources/Claim/Claim.test.js +6 -6
  63. package/src/components/resources/ClaimResponse/ClaimResponse.test.js +6 -6
  64. package/src/components/resources/Condition/Condition.js +63 -47
  65. package/src/components/resources/Condition/Condition.stories.js +41 -8
  66. package/src/components/resources/Condition/Condition.test.js +20 -14
  67. package/src/components/resources/DiagnosticReport/DiagnosticReport.test.js +5 -7
  68. package/src/components/resources/DocumentReference/DocumentReference.js +1 -1
  69. package/src/components/resources/DocumentReference/DocumentReference.test.js +3 -3
  70. package/src/components/resources/Encounter/Encounter.js +66 -36
  71. package/src/components/resources/Encounter/EncounterParticipants.js +2 -2
  72. package/src/components/resources/ExplanationOfBenefit/CareTeam.js +2 -2
  73. package/src/components/resources/ExplanationOfBenefit/Diagnosis.js +15 -5
  74. package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.js +272 -201
  75. package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.test.js +79 -62
  76. package/src/components/resources/ExplanationOfBenefit/Items.js +2 -2
  77. package/src/components/resources/ExplanationOfBenefit/PriceLabel.js +20 -0
  78. package/src/components/resources/ExplanationOfBenefit/Related.js +3 -3
  79. package/src/components/resources/ExplanationOfBenefit/SupportingInfo.js +14 -3
  80. package/src/components/resources/ExplanationOfBenefit/TotalGraph.js +68 -0
  81. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.js +89 -0
  82. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.stories.js +78 -0
  83. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.test.js +51 -0
  84. package/src/components/resources/ExplanationOfBenefitGraph/index.js +3 -0
  85. package/src/components/resources/Goal/Goal.test.js +1 -1
  86. package/src/components/resources/Immunization/Immunization.js +125 -94
  87. package/src/components/resources/Immunization/Immunization.stories.js +23 -4
  88. package/src/components/resources/Immunization/Immunization.test.js +17 -12
  89. package/src/components/resources/List/List.test.js +3 -3
  90. package/src/components/resources/MedicationAdministration/MedicationAdministration.test.js +7 -7
  91. package/src/components/resources/MedicationDispense/MedicationDispense.test.js +2 -2
  92. package/src/components/resources/MedicationRequest/MedicationRequest.test.js +4 -4
  93. package/src/components/resources/Observation/Observation.js +72 -54
  94. package/src/components/resources/Observation/Observation.test.js +6 -18
  95. package/src/components/resources/Observation/ObservationGraph.js +159 -55
  96. package/src/components/resources/Observation/ObservationGraph.test.js +47 -26
  97. package/src/components/resources/Patient/Patient.js +77 -87
  98. package/src/components/resources/Patient/Patient.test.js +1 -1
  99. package/src/components/resources/Practitioner/Practitioner.js +80 -60
  100. package/src/components/resources/Practitioner/Practitioner.test.js +4 -4
  101. package/src/components/resources/Procedure/Procedure.js +99 -87
  102. package/src/components/resources/Procedure/Procedure.stories.js +8 -6
  103. package/src/components/resources/Procedure/Procedure.test.js +11 -8
  104. package/src/components/resources/Questionnaire/Questionnaire.test.js +3 -3
  105. package/src/components/resources/QuestionnaireResponse/QuestionnaireResponse.test.js +5 -5
  106. package/src/components/resources/ReferralRequest/ReferralRequest.test.js +2 -2
  107. package/src/components/resources/ResearchStudy/ResearchStudy.test.js +1 -1
  108. package/src/components/resources/ResourceCategory/ResourceCategory.js +56 -0
  109. package/src/components/resources/ResourceCategory/ResourceCategory.stories.js +29 -0
  110. package/src/components/resources/ResourceCategory/ResourceCategory.test.js +101 -0
  111. package/src/components/resources/ResourceCategory/index.js +3 -0
  112. package/src/components/ui/_header.scss +3 -0
  113. package/src/components/ui/bootstrap-reboot.min.css +2 -22
  114. package/src/components/ui/index.js +191 -29
  115. package/src/constants/badge-status.jsx +98 -0
  116. package/src/fixtures/dstu2/resources/condition/condition.svg +35 -0
  117. package/src/fixtures/dstu2/resources/immunization/immunization.svg +10 -0
  118. package/src/fixtures/example-icons.jsx +169 -0
  119. package/src/index.js +6 -1
  120. package/src/style.scss +176 -0
  121. package/src/utils/formatDate.js +21 -0
  122. package/src/utils/formatDate.test.js +22 -0
  123. package/src/utils/getBadgeColor.js +6 -0
  124. package/src/utils/getBadgeColor.test.js +14 -0
  125. package/src/utils/isUrl.js +9 -0
  126. package/src/utils/isUrl.test.js +12 -0
  127. package/src/utils.js +7 -0
  128. package/build/bootstrap-reboot.min.css +0 -414
  129. package/build/index.js +0 -15
  130. package/build/style.css +0 -459
  131. package/src/components/datatypes/HumanName/HumanName.css +0 -15
  132. package/src/components/datatypes/Reference/Reference.css +0 -8
  133. package/src/components/resources/Observation/ObservationGraph.css +0 -51
  134. package/src/components/resources/Patient/Patient.css +0 -19
  135. 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 { nbspRegex } from '../../../testUtils';
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-08-16');
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-08-16');
42
+ expect(getByTestId('created').textContent).toContain('8/16/2014');
43
+ expect(getByTestId('metricAmount').textContent).toContain('$135.57');
44
44
  expect(
45
- getByTestId('totalCost').textContent.replace(nbspRegex, ' '),
46
- ).toEqual('135.57 USD');
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 { container, getByTestId, queryByTestId } = render(
76
- <ExplanationOfBenefit {...defaultProps} />,
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-08-16');
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
- const tablesContent = [];
93
- getByTestId('hasServices')
94
- .querySelectorAll('.fhir-ui__TableRow')
95
- .forEach(el => {
96
- const tds = [];
97
- el.querySelectorAll('.fhir-ui__TableCell').forEach(item => {
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
- // table 1st row
111
- expect(tablesContent[1]).toEqual([
112
- '(1205)',
113
- '2014-08-16',
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 { container, getByTestId, queryByTestId, queryAllByTestId } = render(
160
- <ExplanationOfBenefit {...defaultProps} />,
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-01-05');
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-01-05; To: 2018-01-05',
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-01-05',
197
- );
198
-
199
- const tablesContent = [];
200
- getByTestId('hasServices')
201
- .querySelectorAll('.fhir-ui__TableRow')
202
- .forEach(el => {
203
- const tds = [];
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
- // table 1st row
218
- expect(tablesContent[1][0]).toContain('(185345009)');
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 label="Category" data-testid="supportingInfo.category">
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 label="Date" data-testid="supportingInfo.timingDate">
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;
@@ -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
+ });
@@ -0,0 +1,3 @@
1
+ import ExplanationOfBenefitGraph from './ExplanationOfBenefitGraph';
2
+
3
+ export default ExplanationOfBenefitGraph;
@@ -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-02-14');
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.',