fhir-react 0.2.1 → 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 (163) 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 +86 -55
  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/Coding/Coding.js +1 -1
  46. package/src/components/datatypes/Date/Date.js +14 -4
  47. package/src/components/datatypes/DatePeriod/DatePeriod.js +38 -0
  48. package/src/components/datatypes/DatePeriod/index.js +3 -0
  49. package/src/components/datatypes/HeaderIcon/HeaderIcon.js +31 -0
  50. package/src/components/datatypes/HeaderIcon/index.js +3 -0
  51. package/src/components/datatypes/HumanName/HumanName.js +6 -21
  52. package/src/components/datatypes/Reference/Reference.js +3 -6
  53. package/src/components/resources/AdverseEvent/AdverseEvent.test.js +2 -2
  54. package/src/components/resources/AllergyIntolerance/AllergyIntolerance.test.js +4 -4
  55. package/src/components/resources/Appointment/Appointment.js +91 -65
  56. package/src/components/resources/Appointment/Appointment.test.js +3 -3
  57. package/src/components/resources/Bundle/Bundle.js +2 -2
  58. package/src/components/resources/Bundle/Bundle.stories.js +78 -12
  59. package/src/components/resources/Bundle/Bundle.test.js +3 -0
  60. package/src/components/resources/CarePlan/CarePlan.test.js +4 -4
  61. package/src/components/resources/CareTeam/CareTeam.js +13 -14
  62. package/src/components/resources/CareTeam/CareTeam.test.js +4 -4
  63. package/src/components/resources/Claim/Claim.test.js +6 -6
  64. package/src/components/resources/ClaimResponse/ClaimResponse.test.js +6 -6
  65. package/src/components/resources/Condition/Condition.js +63 -47
  66. package/src/components/resources/Condition/Condition.stories.js +41 -8
  67. package/src/components/resources/Condition/Condition.test.js +20 -14
  68. package/src/components/resources/DiagnosticReport/DiagnosticReport.test.js +5 -7
  69. package/src/components/resources/DocumentReference/DocumentReference.js +1 -1
  70. package/src/components/resources/DocumentReference/DocumentReference.test.js +3 -3
  71. package/src/components/resources/Encounter/Encounter.js +66 -36
  72. package/src/components/resources/Encounter/EncounterParticipants.js +2 -2
  73. package/src/components/resources/ExplanationOfBenefit/CareTeam.js +2 -2
  74. package/src/components/resources/ExplanationOfBenefit/Diagnosis.js +31 -5
  75. package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.js +272 -201
  76. package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.stories.js +12 -0
  77. package/src/components/resources/ExplanationOfBenefit/ExplanationOfBenefit.test.js +96 -62
  78. package/src/components/resources/ExplanationOfBenefit/Items.js +2 -2
  79. package/src/components/resources/ExplanationOfBenefit/PriceLabel.js +20 -0
  80. package/src/components/resources/ExplanationOfBenefit/Related.js +3 -3
  81. package/src/components/resources/ExplanationOfBenefit/SupportingInfo.js +14 -3
  82. package/src/components/resources/ExplanationOfBenefit/TotalGraph.js +68 -0
  83. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.js +89 -0
  84. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.stories.js +78 -0
  85. package/src/components/resources/ExplanationOfBenefitGraph/ExplanationOfBenefitGraph.test.js +51 -0
  86. package/src/components/resources/ExplanationOfBenefitGraph/index.js +3 -0
  87. package/src/components/resources/Goal/Goal.test.js +1 -1
  88. package/src/components/resources/Immunization/Immunization.js +125 -94
  89. package/src/components/resources/Immunization/Immunization.stories.js +23 -4
  90. package/src/components/resources/Immunization/Immunization.test.js +17 -12
  91. package/src/components/resources/List/DrugTierDefinitionExtension.js +139 -0
  92. package/src/components/resources/List/Entries.js +66 -0
  93. package/src/components/resources/List/List.js +262 -0
  94. package/src/components/resources/List/List.stories.js +75 -0
  95. package/src/components/resources/List/List.test.js +95 -0
  96. package/src/components/resources/List/index.js +3 -0
  97. package/src/components/resources/List/utils.js +6 -0
  98. package/src/components/resources/MedicationAdministration/MedicationAdministration.test.js +7 -7
  99. package/src/components/resources/MedicationDispense/MedicationDispense.test.js +2 -2
  100. package/src/components/resources/MedicationKnowledge/MedicationKnowledge.js +217 -0
  101. package/src/components/resources/MedicationKnowledge/MedicationKnowledge.stories.js +78 -0
  102. package/src/components/resources/MedicationKnowledge/MedicationKnowledge.test.js +69 -0
  103. package/src/components/resources/MedicationKnowledge/index.js +3 -0
  104. package/src/components/resources/MedicationKnowledge/utils.js +8 -0
  105. package/src/components/resources/MedicationRequest/MedicationRequest.test.js +4 -4
  106. package/src/components/resources/Observation/Observation.js +72 -54
  107. package/src/components/resources/Observation/Observation.test.js +6 -18
  108. package/src/components/resources/Observation/ObservationGraph.js +159 -55
  109. package/src/components/resources/Observation/ObservationGraph.test.js +47 -26
  110. package/src/components/resources/Patient/Patient.js +79 -97
  111. package/src/components/resources/Patient/Patient.test.js +10 -10
  112. package/src/components/resources/Practitioner/Practitioner.js +80 -60
  113. package/src/components/resources/Practitioner/Practitioner.test.js +4 -4
  114. package/src/components/resources/Procedure/Procedure.js +99 -87
  115. package/src/components/resources/Procedure/Procedure.stories.js +8 -6
  116. package/src/components/resources/Procedure/Procedure.test.js +11 -8
  117. package/src/components/resources/Questionnaire/Questionnaire.test.js +3 -3
  118. package/src/components/resources/QuestionnaireResponse/QuestionnaireResponse.test.js +5 -5
  119. package/src/components/resources/ReferralRequest/ReferralRequest.test.js +2 -2
  120. package/src/components/resources/ResearchStudy/ResearchStudy.test.js +1 -1
  121. package/src/components/resources/ResourceCategory/ResourceCategory.js +56 -0
  122. package/src/components/resources/ResourceCategory/ResourceCategory.stories.js +29 -0
  123. package/src/components/resources/ResourceCategory/ResourceCategory.test.js +101 -0
  124. package/src/components/resources/ResourceCategory/index.js +3 -0
  125. package/src/components/supportedFhirResourceList.js +4 -0
  126. package/src/components/ui/_header.scss +3 -0
  127. package/src/components/ui/bootstrap-reboot.min.css +2 -22
  128. package/src/components/ui/index.js +191 -29
  129. package/src/constants/badge-status.jsx +98 -0
  130. package/src/fixtures/dstu2/resources/condition/condition.svg +35 -0
  131. package/src/fixtures/dstu2/resources/immunization/immunization.svg +10 -0
  132. package/src/fixtures/dstu2/resources/list/example1.json +49 -0
  133. package/src/fixtures/dstu2/resources/list/example2.json +116 -0
  134. package/src/fixtures/dstu2/resources/list/example3.json +380 -0
  135. package/src/fixtures/example-icons.jsx +169 -0
  136. package/src/fixtures/r4/resources/explanationOfBenefit/c4bbExtendedDiagnosis.json +446 -0
  137. package/src/fixtures/r4/resources/list/example1.json +45 -0
  138. package/src/fixtures/r4/resources/list/example2.json +282 -0
  139. package/src/fixtures/r4/resources/list/example3.json +298 -0
  140. package/src/fixtures/r4/resources/medicationKnowledge/example1.json +42 -0
  141. package/src/fixtures/r4/resources/medicationKnowledge/example2.json +59 -0
  142. package/src/fixtures/r4/resources/medicationKnowledge/example3.json +59 -0
  143. package/src/fixtures/r4/resources/medicationKnowledge/example4.json +59 -0
  144. package/src/fixtures/stu3/resources/list/example1.json +46 -0
  145. package/src/fixtures/stu3/resources/list/example2.json +298 -0
  146. package/src/fixtures/stu3/resources/list/example3.json +115 -0
  147. package/src/index.js +6 -1
  148. package/src/style.scss +176 -0
  149. package/src/utils/formatDate.js +21 -0
  150. package/src/utils/formatDate.test.js +22 -0
  151. package/src/utils/getBadgeColor.js +6 -0
  152. package/src/utils/getBadgeColor.test.js +14 -0
  153. package/src/utils/isUrl.js +9 -0
  154. package/src/utils/isUrl.test.js +12 -0
  155. package/src/utils.js +7 -0
  156. package/build/bootstrap-reboot.min.css +0 -414
  157. package/build/index.js +0 -15
  158. package/build/style.css +0 -459
  159. package/src/components/datatypes/HumanName/HumanName.css +0 -15
  160. package/src/components/datatypes/Reference/Reference.css +0 -8
  161. package/src/components/resources/Observation/ObservationGraph.css +0 -51
  162. package/src/components/resources/Patient/Patient.css +0 -19
  163. package/src/components/ui/index.css +0 -123
@@ -1,68 +1,172 @@
1
1
  import React from 'react';
2
2
  import _get from 'lodash/get';
3
3
  import _isNumber from 'lodash/isNumber';
4
- import './ObservationGraph.css';
5
4
 
6
- const ObservationGraph = props => {
7
- if (props.referenceRange && _isNumber(_get(props, 'valueQuantity.value'))) {
8
- const tooLow = _get(props, 'referenceRange[0].low.value');
9
- const tooHigh = _get(props, 'referenceRange[0].high.value');
5
+ const OBSERVATION_VALUE = 'observationValue';
6
+ const IN_RANGE = 'in range';
7
+ const OUT_OF_RANGE = 'out of range';
8
+
9
+ const rangeBaseClasses = `border border-1 border-light`;
10
+
11
+ const colorfulRange = ({
12
+ width = 1,
13
+ value = '',
14
+ rangeClasses = `bg-primary rounded-pill ${rangeBaseClasses}`,
15
+ }) => {
16
+ return {
17
+ width: width,
18
+ rangeClasses,
19
+ value,
20
+ };
21
+ };
22
+
23
+ const rangeContent = (width, rangeClasses, value, small) => (
24
+ <div
25
+ className={`${rangeClasses} text-white text-center font-source`}
26
+ style={{ width: `${width}%`, lineHeight: '14px', minWidth: '8px' }}
27
+ key={`range${width}-${Math.random()}`}
28
+ data-testid={`rangeContent${value && value.replace(/ /g, '')}`}
29
+ >
30
+ {!small && value}
31
+ </div>
32
+ );
33
+
34
+ const edgeRange = () => rangeContent(2, 'rounded-pill');
35
+
36
+ const observationValuePoint = (actualValue, unit, small) => {
37
+ return (
38
+ <div
39
+ className={`bg-dark ${rangeBaseClasses} rounded-circle ${
40
+ small ? 'mx-0' : 'mx-1'
41
+ }`}
42
+ style={{ width: small ? '9px' : '17px' }}
43
+ key={`ValuePoint-${Math.random()}`}
44
+ >
45
+ {!small && (
46
+ <span
47
+ className={`position-absolute top-0 translate-middle ps-2 ${
48
+ small ? 'pb-4 fs-1' : 'pb-5 fs-4'
49
+ } w-max-content`}
50
+ data-testid="valuePoint"
51
+ >
52
+ {`${actualValue} ${unit}`}
53
+ </span>
54
+ )}
55
+ </div>
56
+ );
57
+ };
58
+
59
+ const valueIsInRange = (actualValue, tooLow, tooHigh, rangeInOneRow, unit) => {
60
+ const firstPart = { width: 1, value: '' };
61
+ const thirdPart = { width: 1, value: '' };
62
+ if (actualValue === tooLow) {
63
+ thirdPart.width = 94;
64
+ thirdPart.value = rangeInOneRow;
65
+ } else if (actualValue === tooHigh) {
66
+ firstPart.width = 94;
67
+ firstPart.value = rangeInOneRow;
68
+ } else {
69
+ const actualRangeWidthLeft = Math.round(
70
+ ((actualValue - tooLow) * 100) / (tooHigh - tooLow),
71
+ );
72
+ const actualRangeWidthRight = 95 - 1 - actualRangeWidthLeft;
73
+
74
+ firstPart.width = actualRangeWidthLeft;
75
+ firstPart.value = `<${tooLow} ${unit}`;
76
+ thirdPart.width = actualRangeWidthRight;
77
+ thirdPart.value = `>${tooHigh} ${unit}`;
78
+ }
79
+
80
+ return [
81
+ colorfulRange(firstPart),
82
+ OBSERVATION_VALUE,
83
+ colorfulRange(thirdPart),
84
+ ];
85
+ };
86
+
87
+ const valueIsOutOfRange = (actualValue, tooLow, tooHigh, rangeInOneRow) => {
88
+ if (actualValue < tooLow) {
89
+ const actualRangeWidth = Math.round(
90
+ ((tooHigh - tooLow) * 100) / (tooHigh - actualValue),
91
+ );
92
+ return [
93
+ OBSERVATION_VALUE,
94
+ colorfulRange({
95
+ width: 95 - actualRangeWidth,
96
+ rangeClasses: 'rounded-pill',
97
+ }),
98
+ colorfulRange({ width: actualRangeWidth, value: rangeInOneRow }),
99
+ ];
100
+ } else {
101
+ const actualRangeWidth = Math.round(
102
+ ((tooHigh - tooLow) * 100) / (actualValue - tooLow),
103
+ );
104
+ return [
105
+ colorfulRange({ width: actualRangeWidth, value: rangeInOneRow }),
106
+ colorfulRange({
107
+ width: 95 - actualRangeWidth,
108
+ rangeClasses: 'rounded-pill',
109
+ }),
110
+ OBSERVATION_VALUE,
111
+ ];
112
+ }
113
+ };
114
+
115
+ const ObservationGraph = ({ referenceRange, valueQuantity, small }) => {
116
+ if (referenceRange && _isNumber(_get(valueQuantity, 'value'))) {
117
+ const tooLow = _get(referenceRange[0], 'low.value');
118
+ const tooHigh = _get(referenceRange[0], 'high.value');
119
+
120
+ const rangeInOneRow = `${tooLow} ${valueQuantity.unit} - ${tooHigh} ${valueQuantity.unit}`;
121
+
10
122
  if (!tooLow || !tooHigh) return null;
11
123
 
12
- const actual = props.valueQuantity.value;
13
- const maxNum = Math.max(tooHigh, actual);
14
- const minNum = Math.min(tooLow, actual);
15
- const startAt = (maxNum - minNum) * -0.5 + minNum;
16
- const endAt = (maxNum - minNum) * 0.5 + maxNum;
17
- const zeroShift = 0 - startAt;
18
- const lowBar = Math.round(((zeroShift + tooLow) / endAt) * 100);
19
- const rangeBar = Math.round(((zeroShift + tooHigh) / endAt) * 100) - lowBar;
20
- const valueBar = Math.round(((zeroShift + actual) / endAt) * 100) + 3;
21
- const higherBar = 100 - rangeBar - lowBar;
124
+ const actualValue = valueQuantity.value;
125
+ const inRange = actualValue >= tooLow && actualValue <= tooHigh;
126
+ const graphRanges = inRange
127
+ ? valueIsInRange(
128
+ actualValue,
129
+ tooLow,
130
+ tooHigh,
131
+ rangeInOneRow,
132
+ valueQuantity.unit,
133
+ )
134
+ : valueIsOutOfRange(actualValue, tooLow, tooHigh, rangeInOneRow);
135
+
136
+ const smallRangeText = inRange ? IN_RANGE : OUT_OF_RANGE;
22
137
 
23
138
  return (
24
- <div className="fhir-resource__ObservationGraph">
139
+ <div
140
+ className={`position-relative ${
141
+ small ? 'w-110 graph-width-sm ms-2 ms-sm-0' : 'my-6'
142
+ }`}
143
+ >
144
+ {small && (
145
+ <span
146
+ className={`pb-1 fs-75 font-source d-inline-block`}
147
+ data-testid="smallRangeText"
148
+ >
149
+ {smallRangeText}
150
+ </span>
151
+ )}
25
152
  <div
26
- className="fhir-resource__ObservationGraph__value-wrapper"
27
- style={{ left: `${valueBar}%` }}
153
+ className="d-flex overflow-hidden bg-gray-200 fs-75 rounded-pill"
154
+ style={{ height: small ? '0.5rem' : '1rem' }}
28
155
  >
29
- <code className="fhir-resource__ObservationGraph__value-actual">
30
- {actual}
31
- </code>
32
- &nbsp;
33
- <span className="fhir-resource__ObservationGraph__value-unit">
34
- {props.valueQuantity.unit}
35
- </span>
36
- </div>
37
- <div className="fhir-resource__ObservationGraph__progress">
38
- <div
39
- className="fhir-resource__ObservationGraph__progress-bar--outside-range"
40
- style={{ width: `${lowBar}%` }}
41
- >
42
- <small>
43
- <strong data-testid="tooLow">{`< ${tooLow}`}</strong>
44
- </small>
45
- </div>
46
- <div
47
- className="fhir-resource__ObservationGraph__progress-bar--inside-range"
48
- role="progressbar"
49
- style={{ width: `${rangeBar}%` }}
50
- aria-valuenow={actual}
51
- aria-valuemin={tooLow}
52
- aria-valuemax={tooHigh}
53
- ></div>
54
- <div
55
- className="fhir-resource__ObservationGraph__progress-value"
56
- style={{ left: `${valueBar}%` }}
57
- ></div>
58
- <div
59
- className="fhir-resource__ObservationGraph__progress-bar--outside-range"
60
- style={{ width: `${higherBar}%` }}
61
- >
62
- <small>
63
- <strong data-testid="tooHigh">{`> ${tooHigh}`}</strong>
64
- </small>
65
- </div>
156
+ {edgeRange()}
157
+ {graphRanges.map(graphRange => {
158
+ if (graphRange === OBSERVATION_VALUE)
159
+ return observationValuePoint(
160
+ actualValue,
161
+ valueQuantity.unit,
162
+ small,
163
+ inRange,
164
+ );
165
+
166
+ const { rangeClasses, width, value } = graphRange;
167
+ return rangeContent(width, rangeClasses, value, small);
168
+ })}
169
+ {edgeRange()}
66
170
  </div>
67
171
  </div>
68
172
  );
@@ -3,39 +3,41 @@ import { render } from '@testing-library/react';
3
3
  import ObservationGraph from './ObservationGraph';
4
4
 
5
5
  describe('ObservationGraph component', () => {
6
- it('should render component correctly with proper props', () => {
7
- const defaultProps = {
8
- referenceRange: [
9
- {
10
- low: {
11
- unit: '%',
12
- code: '%',
13
- system: 'http://unitsofmeasure.org',
14
- value: 41,
15
- },
16
- high: {
17
- unit: '%',
18
- code: '%',
19
- system: 'http://unitsofmeasure.org',
20
- value: 53,
21
- },
22
- text: '41 - 53 %',
6
+ const defaultProps = {
7
+ referenceRange: [
8
+ {
9
+ low: {
10
+ unit: '%',
11
+ code: '%',
12
+ system: 'http://unitsofmeasure.org',
13
+ value: 41,
14
+ },
15
+ high: {
16
+ unit: '%',
17
+ code: '%',
18
+ system: 'http://unitsofmeasure.org',
19
+ value: 53,
23
20
  },
24
- ],
25
- valueQuantity: {
26
- unit: '%',
27
- code: '%',
28
- system: 'http://unitsofmeasure.org',
29
- value: 41,
21
+ text: '41 - 53 %',
30
22
  },
31
- };
23
+ ],
24
+ valueQuantity: {
25
+ unit: '%',
26
+ code: '%',
27
+ system: 'http://unitsofmeasure.org',
28
+ value: 41,
29
+ },
30
+ };
31
+ it('should render component correctly with proper props', () => {
32
32
  const { container, getByTestId } = render(
33
33
  <ObservationGraph {...defaultProps} />,
34
34
  );
35
35
 
36
36
  expect(container).not.toBeNull();
37
- expect(getByTestId('tooLow').textContent).toContain('41');
38
- expect(getByTestId('tooHigh').textContent).toContain('53');
37
+ expect(getByTestId('valuePoint').textContent).toContain('41 %');
38
+ expect(getByTestId('rangeContent41%-53%').textContent).toContain(
39
+ '41 % - 53 %',
40
+ );
39
41
  });
40
42
 
41
43
  it('should return empty component without required props', () => {
@@ -45,4 +47,23 @@ describe('ObservationGraph component', () => {
45
47
  expect(container).not.toBeNull();
46
48
  expect(container.hasChildNodes()).toBeFalsy();
47
49
  });
50
+ it('should render component correctly with small props and in range value', () => {
51
+ defaultProps.small = true;
52
+ const { container, getByTestId } = render(
53
+ <ObservationGraph {...defaultProps} />,
54
+ );
55
+
56
+ expect(container).not.toBeNull();
57
+ expect(getByTestId('smallRangeText').textContent).toContain('in range');
58
+ });
59
+ it('should render component correctly with small props and out of range value', () => {
60
+ defaultProps.small = true;
61
+ defaultProps.valueQuantity.value = 39;
62
+ const { container, getByTestId } = render(
63
+ <ObservationGraph {...defaultProps} />,
64
+ );
65
+
66
+ expect(container).not.toBeNull();
67
+ expect(getByTestId('smallRangeText').textContent).toContain('out of range');
68
+ });
48
69
  });
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import _get from 'lodash/get';
4
+ import _isEmpty from 'lodash/isEmpty';
4
5
  import md5 from 'md5';
5
6
 
6
7
  import HumanName from '../../datatypes/HumanName';
@@ -8,8 +9,8 @@ import Telecom from '../../datatypes/Telecom';
8
9
  import Address from '../../datatypes/Address';
9
10
  import Coding from '../../datatypes/Coding';
10
11
  import Date from '../../datatypes/Date';
11
- import { Root, Header, Body, Value, MissingValue, Badge } from '../../ui';
12
- import './Patient.css';
12
+ import Accordion from '../../containers/Accordion';
13
+ import { Root, Header, MissingValue, Badge, Body } from '../../ui';
13
14
 
14
15
  export function PatientContact(props) {
15
16
  const { fhirData } = props;
@@ -20,7 +21,7 @@ export function PatientContact(props) {
20
21
  <div>
21
22
  <HumanName fhirData={name} />
22
23
  {relationship && (
23
- <small className="fhir-resource__Patient__patientContact-relationship">{` (${relationship})`}</small>
24
+ <small className="text-bold">{` (${relationship})`}</small>
24
25
  )}
25
26
  </div>
26
27
  );
@@ -30,7 +31,7 @@ export function getId(fhirResource) {
30
31
  return _get(fhirResource, 'id');
31
32
  }
32
33
  export function getNames(fhirResource) {
33
- return _get(fhirResource, 'name', []);
34
+ return _get(fhirResource, 'name.0', null);
34
35
  }
35
36
  export function getBirthDate(fhirResource) {
36
37
  return _get(fhirResource, 'birthDate');
@@ -45,7 +46,7 @@ function Patient(props) {
45
46
  const id = getId(fhirResource);
46
47
  const idHash = md5(id || '');
47
48
  const avatarSrc = `http://www.gravatar.com/avatar/${idHash}?s=50&r=any&default=identicon&forcedefault=1`;
48
- const patientNames = getNames(fhirResource);
49
+ const patientName = getNames(fhirResource);
49
50
  const patientBirthDate = getBirthDate(fhirResource);
50
51
  const patientGender = getGender(fhirResource);
51
52
  const patientContact = _get(fhirResource, 'contact[0]');
@@ -57,7 +58,8 @@ function Patient(props) {
57
58
  .filter(item => Boolean(_get(item, 'language.coding', null)))
58
59
  .map(item => item.language.coding);
59
60
  communicationLanguage = _get(communicationLanguage, '0', []);
60
- const hasCommunicationLanguage = communicationLanguage.length > 0;
61
+ const hasCommunicationLanguage = !_isEmpty(communicationLanguage);
62
+ const hasPatientPhones = !_isEmpty(patientPhones);
61
63
  const active = _get(fhirResource, 'active', false);
62
64
  const activeStatus = active ? 'active' : 'inactive';
63
65
  const deceasedBoolean = _get(fhirResource, 'deceasedBoolean', false);
@@ -68,100 +70,80 @@ function Patient(props) {
68
70
  return <HumanName fhirData={patientName} primary={index === 0} />;
69
71
  };
70
72
 
73
+ const tableData = [
74
+ {
75
+ label: 'Patient contact',
76
+ testId: 'patientContact',
77
+ data: patientContact && <PatientContact fhirData={patientContact} />,
78
+ status: patientContact,
79
+ },
80
+ {
81
+ label: 'Deceased',
82
+ testId: 'deceasedInfo',
83
+ data: deceasedDate ? <Date fhirData={deceasedDate} /> : 'yes',
84
+ status: !patientSimple && isDeceased,
85
+ },
86
+ {
87
+ label: 'Address',
88
+ testId: 'patientAddress',
89
+ data: patientAddress && <Address fhirData={patientAddress} />,
90
+ status: !patientSimple && patientAddress,
91
+ },
92
+ {
93
+ label: 'Telephone',
94
+ testId: 'patientPhones',
95
+ data: !hasPatientPhones ? (
96
+ <MissingValue />
97
+ ) : (
98
+ patientPhones.map((telecom, index) => (
99
+ <Telecom key={index} fhirData={telecom} />
100
+ ))
101
+ ),
102
+ status: !patientSimple,
103
+ },
104
+ {
105
+ label: 'Communication - language',
106
+ testId: 'communicationLanguage',
107
+ data:
108
+ hasCommunicationLanguage &&
109
+ communicationLanguage.map((item, i) => (
110
+ <Coding key={`item-${i}`} fhirData={item} />
111
+ )),
112
+ status: !patientSimple && hasCommunicationLanguage,
113
+ },
114
+ ];
115
+
71
116
  return (
72
117
  <Root name="Patient">
73
- <Header>
74
- <div className="fhir-resource__Patient__patient-block">
75
- <div>
76
- <img
77
- className="fhir-resource__Patient__patient-avatar"
78
- src={avatarSrc}
79
- alt=""
80
- />
81
- </div>
82
- <div>
83
- <div className="fhir-resource__Patient__patient-block">
84
- {patientNames.map((patientName, index) => {
85
- if (props.thorough === false && index !== 0) {
86
- return null;
87
- } else {
88
- return (
89
- <React.Fragment key={index}>
90
- <span data-testid={`patientName-${index}`}>
91
- {renderName
92
- ? renderName({
93
- patientName,
94
- defaultName,
95
- fhirVersion,
96
- id,
97
- })
98
- : defaultName(patientName, index)}
99
- </span>
100
- &nbsp;&nbsp;
101
- </React.Fragment>
102
- );
103
- }
104
- })}
105
- </div>
106
- <div>
107
- {active && (
108
- <Badge data-testid="activeStatus">{activeStatus}</Badge>
109
- )}
110
- {patientBirthDate && (
111
- <span className="fhir-resource__Patient__BirthDate-block">
112
- <strong>
113
- <span data-testid="patientGender">
114
- {patientGender || 'unknown'}
115
- </span>
116
- {', '}
117
- <span data-testid="patientBirthDate">
118
- {patientBirthDate}
119
- </span>
120
- </strong>
121
- <small> (DOB)</small>
118
+ <Accordion
119
+ headerContent={
120
+ <Header
121
+ resourceName={fhirResource.resourceType}
122
+ additionalContent={
123
+ patientBirthDate && (
124
+ <span className="text-gray-600">
125
+ <span data-testid="patientGender" className="text-capitalize">
126
+ {patientGender || 'unknown'}
127
+ </span>
128
+ {', '}
129
+ <span data-testid="patientBirthDate">{patientBirthDate}</span>
122
130
  </span>
123
- )}
124
- </div>
125
- <div>
126
- {patientContact && <PatientContact fhirData={patientContact} />}
127
- </div>
128
- </div>
129
- </div>
130
- </Header>
131
- {!patientSimple && (
132
- <Body>
133
- {isDeceased && (
134
- <Value label="Deceased" data-testid="deceasedInfo">
135
- {deceasedDate ? <Date fhirData={deceasedDate} /> : 'yes'}
136
- </Value>
137
- )}
138
- {patientAddress && (
139
- <Value label="Address" data-testid="patientAddress">
140
- <Address fhirData={patientAddress} />
141
- </Value>
142
- )}
143
- {patientPhones && (
144
- <Value label="TELEPHONE" data-testid="patientPhones">
145
- {patientPhones.map((telecom, index) => (
146
- <div key={index}>
147
- <Telecom fhirData={telecom} />
148
- </div>
149
- ))}
150
- {patientPhones.length === 0 && <MissingValue />}
151
- </Value>
152
- )}
153
- {hasCommunicationLanguage && (
154
- <Value
155
- label="Communication - language"
156
- data-testid="communicationLanguage"
157
- >
158
- {communicationLanguage.map((item, i) => (
159
- <Coding key={`item-${i}`} fhirData={item} />
160
- ))}
161
- </Value>
162
- )}
163
- </Body>
164
- )}
131
+ )
132
+ }
133
+ icon={avatarSrc}
134
+ badges={
135
+ active && <Badge data-testid="activeStatus">{activeStatus}</Badge>
136
+ }
137
+ title={
138
+ renderName
139
+ ? renderName({ patientName, defaultName, fhirVersion, id })
140
+ : defaultName(patientName, 0)
141
+ }
142
+ titleTestID="patientName"
143
+ />
144
+ }
145
+ bodyContent={<Body tableData={tableData} />}
146
+ />
165
147
  </Root>
166
148
  );
167
149
  }
@@ -17,9 +17,9 @@ describe('should render component correctly', () => {
17
17
  <Patient {...defaultProps} />,
18
18
  );
19
19
 
20
- expect(
21
- getByTestId('patientName-0').textContent.replace(/\s+/g, ' '),
22
- ).toEqual('Jason Argonaut (usual)');
20
+ expect(getByTestId('patientName').textContent.replace(/\s+/g, ' ')).toEqual(
21
+ 'Jason Argonaut (usual)',
22
+ );
23
23
  expect(getByTestId('patientGender').textContent).toEqual('male');
24
24
  expect(getByTestId('patientBirthDate').textContent).toEqual('1985-08-01');
25
25
  expect(getByTestId('patientAddress').textContent).toEqual(
@@ -40,9 +40,9 @@ describe('should render component correctly', () => {
40
40
  <Patient {...defaultProps} />,
41
41
  );
42
42
 
43
- expect(
44
- getByTestId('patientName-0').textContent.replace(/\s+/g, ' '),
45
- ).toEqual('John, X Doe (usual)');
43
+ expect(getByTestId('patientName').textContent.replace(/\s+/g, ' ')).toEqual(
44
+ 'John, X Doe (usual)',
45
+ );
46
46
  expect(getByTestId('patientGender').textContent).toEqual('male');
47
47
  expect(getByTestId('patientBirthDate').textContent).toEqual('2014-06-01');
48
48
  expect(getByTestId('patientAddress').textContent).toEqual(' 05 99999 ');
@@ -67,9 +67,9 @@ describe('should render component correctly', () => {
67
67
  <Patient {...defaultProps} />,
68
68
  );
69
69
 
70
- expect(
71
- getByTestId('patientName-0').textContent.replace(/\s+/g, ' '),
72
- ).toEqual('Peter, James Chalmers (official)');
70
+ expect(getByTestId('patientName').textContent.replace(/\s+/g, ' ')).toEqual(
71
+ 'Peter, James Chalmers (official)',
72
+ );
73
73
  expect(getByTestId('patientGender').textContent).toEqual('male');
74
74
  expect(getByTestId('patientBirthDate').textContent).toEqual('1974-12-25');
75
75
  expect(getByTestId('patientAddress').textContent).toContain(
@@ -87,6 +87,6 @@ describe('should render component correctly', () => {
87
87
  fhirResource: example3PatientR4,
88
88
  };
89
89
  const { getByTestId } = render(<Patient {...defaultProps} />);
90
- expect(getByTestId('deceasedInfo').textContent).toEqual('2015-02-14');
90
+ expect(getByTestId('deceasedInfo').textContent).toEqual('2/14/2015');
91
91
  });
92
92
  });