fhir-react 0.2.2 → 0.3.1

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 (138) hide show
  1. package/.github/workflows/publish_npmjs.yml +20 -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 +41 -13
  6. package/build/bootstrap-reboot.min.css +2 -22
  7. package/build/index.js +38 -3
  8. package/build/style.css +11 -227
  9. package/package.json +13 -4
  10. package/src/assets/common/chevron-right.svg +3 -0
  11. package/src/assets/containers/AllergyIntolerance/allergy-intolerance.svg +9 -0
  12. package/src/assets/containers/Appointment/appointment.svg +14 -0
  13. package/src/assets/containers/CarePlan/care-plan.svg +10 -0
  14. package/src/assets/containers/CareTeam/care-team.svg +10 -0
  15. package/src/assets/containers/Claim/claim.svg +6 -0
  16. package/src/assets/containers/ClaimResponse/claim-response.svg +7 -0
  17. package/src/assets/containers/Condition/condition.svg +11 -0
  18. package/src/assets/containers/Device/device.svg +8 -0
  19. package/src/assets/containers/DiagnosticReport/diagnostic-report.svg +14 -0
  20. package/src/assets/containers/DocumentReference/document-reference.svg +10 -0
  21. package/src/assets/containers/Encounter/encounter.svg +10 -0
  22. package/src/assets/containers/ExplanationOfBenefit/explanation-of-benefit.svg +3 -0
  23. package/src/assets/containers/FamilyMemberHistory/family-member-history.svg +7 -0
  24. package/src/assets/containers/Goal/goal.svg +11 -0
  25. package/src/assets/containers/Immunization/immunization.svg +7 -0
  26. package/src/assets/containers/List/list.svg +3 -0
  27. package/src/assets/containers/Location/location.svg +4 -0
  28. package/src/assets/containers/Medication/medication.svg +5 -0
  29. package/src/assets/containers/MedicationAdministration/medication-administration.svg +6 -0
  30. package/src/assets/containers/MedicationKnowledge/medication-knowledge.svg +11 -0
  31. package/src/assets/containers/MedicationStatement/medication-statement.svg +5 -0
  32. package/src/assets/containers/Observation/observation.svg +12 -0
  33. package/src/assets/containers/Practitioner/practitioner.svg +5 -0
  34. package/src/assets/containers/Procedure/procedure.svg +9 -0
  35. package/src/assets/containers/Questionnaire/questionnaire.svg +6 -0
  36. package/src/assets/containers/QuestionnaireResponse/questionnaire-response.svg +6 -0
  37. package/src/assets/containers/QustionnaireResponse/questionnaire-response.svg +6 -0
  38. package/src/assets/containers/ResearchStudy/research-study.svg +9 -0
  39. package/src/assets/containers/ResourceCategory/resource-placeholder.svg +3 -0
  40. package/src/components/containers/Accordion/Accordion.js +80 -0
  41. package/src/components/containers/Accordion/Accordion.stories.js +76 -0
  42. package/src/components/containers/Accordion/index.js +3 -0
  43. package/src/components/containers/ResourceContainer/ResourceContainer.css +0 -1
  44. package/src/components/containers/ResourceContainer/ResourceContainer.js +1 -1
  45. package/src/components/datatypes/AccountBalance/AccountBalance.js +33 -0
  46. package/src/components/datatypes/AccountBalance/index.js +3 -0
  47. package/src/components/datatypes/Annotation/Annotation.js +1 -1
  48. package/src/components/datatypes/Coding/Coding.js +1 -1
  49. package/src/components/datatypes/Date/Date.js +14 -4
  50. package/src/components/datatypes/DatePeriod/DatePeriod.js +38 -0
  51. package/src/components/datatypes/DatePeriod/index.js +3 -0
  52. package/src/components/datatypes/HeaderIcon/HeaderIcon.js +31 -0
  53. package/src/components/datatypes/HeaderIcon/index.js +3 -0
  54. package/src/components/datatypes/HumanName/HumanName.js +6 -21
  55. package/src/components/datatypes/Reference/Reference.js +3 -6
  56. package/src/components/resources/AdverseEvent/AdverseEvent.test.js +2 -2
  57. package/src/components/resources/AllergyIntolerance/AllergyIntolerance.test.js +4 -4
  58. package/src/components/resources/Appointment/Appointment.js +91 -65
  59. package/src/components/resources/Appointment/Appointment.test.js +3 -3
  60. package/src/components/resources/Bundle/Bundle.js +2 -2
  61. package/src/components/resources/Bundle/Bundle.stories.js +78 -12
  62. package/src/components/resources/Bundle/Bundle.test.js +3 -0
  63. package/src/components/resources/CarePlan/CarePlan.test.js +4 -4
  64. package/src/components/resources/CareTeam/CareTeam.js +13 -14
  65. package/src/components/resources/CareTeam/CareTeam.test.js +4 -4
  66. package/src/components/resources/Claim/Claim.test.js +6 -6
  67. package/src/components/resources/ClaimResponse/ClaimResponse.test.js +6 -6
  68. package/src/components/resources/Condition/Condition.js +63 -47
  69. package/src/components/resources/Condition/Condition.stories.js +41 -8
  70. package/src/components/resources/Condition/Condition.test.js +20 -14
  71. package/src/components/resources/DiagnosticReport/DiagnosticReport.test.js +5 -7
  72. package/src/components/resources/DocumentReference/DocumentReference.js +1 -1
  73. package/src/components/resources/DocumentReference/DocumentReference.test.js +3 -3
  74. package/src/components/resources/Encounter/Encounter.js +66 -36
  75. package/src/components/resources/Encounter/EncounterParticipants.js +2 -2
  76. package/src/components/resources/ExplanationOfBenefit/CareTeam.js +2 -2
  77. package/src/components/resources/ExplanationOfBenefit/Diagnosis.js +31 -5
  78. package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.js +272 -201
  79. package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.stories.js +12 -0
  80. package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.test.js +96 -62
  81. package/src/components/resources/ExplanationOfBenefit/Items.js +2 -2
  82. package/src/components/resources/ExplanationOfBenefit/PriceLabel.js +20 -0
  83. package/src/components/resources/ExplanationOfBenefit/Related.js +3 -3
  84. package/src/components/resources/ExplanationOfBenefit/SupportingInfo.js +14 -3
  85. package/src/components/resources/ExplanationOfBenefit/TotalGraph.js +68 -0
  86. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.js +89 -0
  87. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.stories.js +78 -0
  88. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.test.js +51 -0
  89. package/src/components/resources/ExplanationOfBenefitGraph/index.js +3 -0
  90. package/src/components/resources/Goal/Goal.test.js +1 -1
  91. package/src/components/resources/Immunization/Immunization.js +125 -94
  92. package/src/components/resources/Immunization/Immunization.stories.js +23 -4
  93. package/src/components/resources/Immunization/Immunization.test.js +17 -12
  94. package/src/components/resources/List/List.test.js +3 -3
  95. package/src/components/resources/MedicationAdministration/MedicationAdministration.test.js +7 -7
  96. package/src/components/resources/MedicationDispense/MedicationDispense.test.js +2 -2
  97. package/src/components/resources/MedicationRequest/MedicationRequest.test.js +4 -4
  98. package/src/components/resources/Observation/Observation.js +72 -54
  99. package/src/components/resources/Observation/Observation.test.js +6 -18
  100. package/src/components/resources/Observation/ObservationGraph.js +159 -55
  101. package/src/components/resources/Observation/ObservationGraph.test.js +47 -26
  102. package/src/components/resources/Patient/Patient.js +79 -97
  103. package/src/components/resources/Patient/Patient.test.js +10 -10
  104. package/src/components/resources/Practitioner/Practitioner.js +80 -60
  105. package/src/components/resources/Practitioner/Practitioner.test.js +4 -4
  106. package/src/components/resources/Procedure/Procedure.js +99 -87
  107. package/src/components/resources/Procedure/Procedure.stories.js +8 -6
  108. package/src/components/resources/Procedure/Procedure.test.js +11 -8
  109. package/src/components/resources/Questionnaire/Questionnaire.test.js +3 -3
  110. package/src/components/resources/QuestionnaireResponse/QuestionnaireResponse.test.js +5 -5
  111. package/src/components/resources/ReferralRequest/ReferralRequest.test.js +2 -2
  112. package/src/components/resources/ResearchStudy/ResearchStudy.test.js +1 -1
  113. package/src/components/resources/ResourceCategory/ResourceCategory.js +56 -0
  114. package/src/components/resources/ResourceCategory/ResourceCategory.stories.js +29 -0
  115. package/src/components/resources/ResourceCategory/ResourceCategory.test.js +101 -0
  116. package/src/components/resources/ResourceCategory/index.js +3 -0
  117. package/src/components/ui/_header.scss +3 -0
  118. package/src/components/ui/bootstrap-reboot.min.css +2 -22
  119. package/src/components/ui/index.js +191 -29
  120. package/src/constants/badge-status.jsx +98 -0
  121. package/src/fixtures/dstu2/resources/condition/condition.svg +35 -0
  122. package/src/fixtures/dstu2/resources/immunization/immunization.svg +10 -0
  123. package/src/fixtures/example-icons.jsx +169 -0
  124. package/src/fixtures/r4/resources/explanationOfBenefit/c4bbExtendedDiagnosis.json +446 -0
  125. package/src/index.js +6 -1
  126. package/src/style.scss +176 -0
  127. package/src/utils/formatDate.js +21 -0
  128. package/src/utils/formatDate.test.js +22 -0
  129. package/src/utils/getBadgeColor.js +6 -0
  130. package/src/utils/getBadgeColor.test.js +14 -0
  131. package/src/utils/isUrl.js +9 -0
  132. package/src/utils/isUrl.test.js +12 -0
  133. package/src/utils.js +7 -0
  134. package/src/components/datatypes/HumanName/HumanName.css +0 -15
  135. package/src/components/datatypes/Reference/Reference.css +0 -8
  136. package/src/components/resources/Observation/ObservationGraph.css +0 -51
  137. package/src/components/resources/Patient/Patient.css +0 -19
  138. package/src/components/ui/index.css +0 -123
@@ -1,14 +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';
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';
12
12
 
13
13
  describe('should render ExplanationOfBenefit component properly', () => {
14
14
  it('should render with DSTU2 source data', () => {
@@ -23,7 +23,7 @@ describe('should render ExplanationOfBenefit component properly', () => {
23
23
  expect(container).not.toBeNull();
24
24
 
25
25
  expect(getByTestId('title').textContent).toContain('Claim settled as ');
26
- expect(getByTestId('created').textContent).toContain('2014-08-16');
26
+ expect(getByTestId('created').textContent).toContain('8/16/2014');
27
27
  expect(getByTestId('insurer').textContent).toContain('Organization/2');
28
28
  });
29
29
 
@@ -39,13 +39,11 @@ describe('should render ExplanationOfBenefit component properly', () => {
39
39
  expect(container).not.toBeNull();
40
40
 
41
41
  expect(getByTestId('title').textContent).toContain('Claim settled as ');
42
- expect(getByTestId('created').textContent).toContain('2014-08-16');
43
- expect(
44
- getByTestId('totalCost').textContent.replace(nbspRegex, ' '),
45
- ).toEqual('135.57 USD');
42
+ expect(getByTestId('created').textContent).toContain('8/16/2014');
43
+ expect(getByTestId('metricAmount').textContent).toContain('$135.57');
46
44
  expect(
47
- getByTestId('totalBenefit').textContent.replace(nbspRegex, ' '),
48
- ).toContain('96 USD');
45
+ getByTestId('planDiscount').textContent.replace(nbspRegex, ' '),
46
+ ).toContain('$96.00');
49
47
  expect(getByTestId('hasServices').textContent).toContain('(1200)');
50
48
  });
51
49
 
@@ -71,15 +69,19 @@ describe('should render ExplanationOfBenefit component properly', () => {
71
69
  fhirVersion: fhirVersions.R4,
72
70
  };
73
71
 
74
- const { container, getByTestId, queryByTestId } = render(
75
- <ExplanationOfBenefit {...defaultProps} />,
76
- );
72
+ const {
73
+ container,
74
+ getByTestId,
75
+ queryByTestId,
76
+ getAllByTestId,
77
+ getAllByRole,
78
+ } = render(<ExplanationOfBenefit {...defaultProps} />);
77
79
  expect(container).not.toBeNull();
78
80
 
79
81
  expect(getByTestId('title').textContent).toEqual(
80
82
  'Claim settled as per contract.',
81
83
  );
82
- expect(getByTestId('created').textContent).toEqual('2014-08-16');
84
+ expect(getByTestId('created').textContent).toEqual('8/16/2014');
83
85
  expect(getByTestId('insurer').textContent).toEqual('Organization/3');
84
86
  expect(getByTestId('provider').textContent).toEqual('Practitioner/1');
85
87
  expect(getByTestId('totalSum').textContent).toContain('135.57');
@@ -88,38 +90,43 @@ describe('should render ExplanationOfBenefit component properly', () => {
88
90
  expect(getByTestId('insurance').textContent).toEqual('Coverage/9876B1');
89
91
 
90
92
  expect(queryByTestId('hasServices')).not.toBeNull();
91
- const tablesContent = [];
92
- getByTestId('hasServices')
93
- .querySelectorAll('.fhir-ui__TableRow')
94
- .forEach(el => {
95
- const tds = [];
96
- el.querySelectorAll('.fhir-ui__TableCell').forEach(item => {
97
- tds.push(String(item.textContent).trim());
98
- });
99
- tablesContent.push(tds);
100
- });
101
- // table header
102
- 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([
103
99
  'Service',
104
100
  'Service date',
105
101
  'Quantity',
106
102
  'Item cost',
107
103
  ]);
108
104
 
109
- // table 1st row
110
- expect(tablesContent[1]).toEqual([
111
- '(1205)',
112
- '2014-08-16',
113
- '-',
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([
114
126
  `135.57${String.fromCharCode(160)}USD`,
115
- ]);
116
- // table 2nd row
117
- expect(tablesContent[2]).toEqual([
118
- '(group)',
119
- '2014-08-16',
120
- '-',
121
127
  `200${String.fromCharCode(160)}USD`,
122
128
  ]);
129
+
123
130
  expect(queryByTestId('hasInformation')).toBeNull();
124
131
  expect(queryByTestId('totalBenefit')).toBeNull();
125
132
  expect(queryByTestId('totalCost')).toBeNull();
@@ -155,12 +162,17 @@ describe('should render ExplanationOfBenefit component properly', () => {
155
162
  withCarinBBProfile: true,
156
163
  };
157
164
 
158
- const { container, getByTestId, queryByTestId, queryAllByTestId } = render(
159
- <ExplanationOfBenefit {...defaultProps} />,
160
- );
165
+ const {
166
+ container,
167
+ getByTestId,
168
+ queryByTestId,
169
+ queryAllByTestId,
170
+ getAllByTestId,
171
+ getAllByRole,
172
+ } = render(<ExplanationOfBenefit {...defaultProps} />);
161
173
  expect(container).not.toBeNull();
162
174
 
163
- expect(getByTestId('created').textContent).toEqual('2017-01-05');
175
+ expect(getByTestId('created').textContent).toEqual('1/5/2017');
164
176
  expect(getByTestId('identifier').textContent).toContain(
165
177
  'c145d3fe-d56e-dc26-75e9-01e90672f506',
166
178
  );
@@ -175,7 +187,7 @@ describe('should render ExplanationOfBenefit component properly', () => {
175
187
  'Organization/iAxXvHiphwGGAL48m3B7XXtKlLZg6yXnC1ch84x1up',
176
188
  );
177
189
  expect(getByTestId('billablePeriod').textContent).toEqual(
178
- 'From: 2017-01-05; To: 2018-01-05',
190
+ 'From: 1/5/2017; To: 1/5/2018',
179
191
  );
180
192
  expect(getByTestId('patient').textContent).toEqual(
181
193
  'Patient/f56391c2-dd54-b378-46ef-87c1643a2ba0',
@@ -192,32 +204,54 @@ describe('should render ExplanationOfBenefit component properly', () => {
192
204
  'clmrecvddate',
193
205
  );
194
206
  expect(getByTestId('supportingInfo.timingDate').textContent).toEqual(
195
- '2017-01-05',
196
- );
197
-
198
- const tablesContent = [];
199
- getByTestId('hasServices')
200
- .querySelectorAll('.fhir-ui__TableRow')
201
- .forEach(el => {
202
- const tds = [];
203
- el.querySelectorAll('.fhir-ui__TableCell').forEach(item => {
204
- tds.push(String(item.textContent).trim());
205
- });
206
- tablesContent.push(tds);
207
- });
208
- // table header
209
- 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([
210
215
  'Service',
211
216
  'Service date',
212
217
  'Quantity',
213
218
  'Item cost',
214
219
  ]);
215
220
 
216
- // table 1st row
217
- 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
+ });
218
236
 
219
237
  expect(queryAllByTestId('items.level')).not.toBeNull();
220
238
  expect(queryAllByTestId('items.sequence')).not.toBeNull();
221
239
  expect(queryAllByTestId('items.sequence')).toHaveLength(3);
222
240
  });
241
+
242
+ it('should render C4BB diagnosis fields', () => {
243
+ const defaultProps = {
244
+ fhirResource: exampleC4BBExtendedDiagnosis,
245
+ fhirVersion: fhirVersions.R4,
246
+ withCarinBBProfile: true,
247
+ };
248
+
249
+ const { container, queryByTestId, getByTestId } = render(
250
+ <ExplanationOfBenefit {...defaultProps} />,
251
+ );
252
+ expect(container).not.toBeNull();
253
+
254
+ expect(getByTestId('diagnosisOnAdmission').textContent).toContain('?');
255
+ expect(queryByTestId('diagnosisPackageCode')).toBeNull();
256
+ });
223
257
  });
@@ -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.',